Documentation beta

How to use the scanner - the web app and the JSON API. Results are informational only, produced by automated tests.

Beta access beta

During the closed beta the scanner is invite-only. You need a personal access code - it looks like bk-…. No code yet? Request beta access.

Web app beta

API beta

Send your code on every request, in either header (X-Access-Key or a Bearer token):

curl -s -X POST https://scan.stalkphish.io/api/scan \
  -H 'Content-Type: application/json' \
  -H 'X-Access-Key: bk-yourcode' \
  -d '{"url":"https://suspicious-example.com/login"}'

# equivalently:
#   -H 'Authorization: Bearer bk-yourcode'
ResponseMeaning
202Accepted - a scan was queued.
401Missing, invalid, or revoked access code.
429Your code's quota is spent - see the Retry-After header.

Each code has its own quota. Please scan responsibly, and use “⚑ Report a false positive or issue” on any wrong verdict - that feedback is exactly what the beta is for.

Using the web app

  1. Paste a URL on the Scan page and press Scan URL. Defanged URLs are accepted - hxxps://evil[.]com is read as https://evil.com. If you omit the scheme, https:// is assumed.
  2. The scan runs asynchronously. You are taken to a result page that updates live: Queued… → the current phase (Resolving DNS & hosting, Querying threat intelligence, Fetching & analysing the page, Rendering the page…) → the final result.
  3. Under heavy load a submission may be rejected with a “scanner is busy” message - retry in a moment.

The web app never fetches the target from your browser. The URL is analysed from isolated infrastructure, through proxies - your IP is never used.

Reading a result

The header shows a threat score out of 100 - higher means more evidence of phishing, not more trust - and a verdict:

VerdictScoreMeaning
CRITICAL80–100Almost certainly phishing.
HIGH55–79Strong phishing indicators.
MEDIUM35–54Suspicious - investigate further.
LOW18–34Weak signals.
VERY LOW0–17No significant indicators.

The report is organised in sections:

Every hostile URL is shown defanged (hxxp://…[.]…) so it can't be clicked by accident. Each result also carries the scan time (UTC).

Think a verdict is wrong? Use “⚑ Report a false positive or issue” at the bottom of the result - it attaches the exact scan so we can reproduce and tune.

Limits & privacy

API

Everything the web app does is available over a JSON API. The interactive reference is Swagger UI:

beta Authentication required. Every POST /api/scan needs your access code (X-Access-Key: bk-…) - see Beta access above.

Asynchronous flow

A scan is a job: submit a URL, receive a uuid, then poll until it's done.

MethodPathPurpose
POST/api/scanSubmit a URL → 202 {uuid, status:"queued"}
GET/api/scan/{uuid}Status & result (queued → running → done | error)
GET/api/scan/{uuid}/screenshotRendered screenshot (PNG)
POST/api/reportReport a false positive / issue on a scan

Example

# 1. Submit - returns a UUID immediately
curl -s -X POST https://scan.stalkphish.io/api/scan \
  -H 'Content-Type: application/json' \
  -H 'X-Access-Key: bk-yourcode' \
  -d '{"url":"https://suspicious-example.com/login"}'
# → {"uuid":"a1b2c3d4-...","status":"queued"}

# 2. Poll until status == "done"
curl -s https://scan.stalkphish.io/api/scan/a1b2c3d4-...
# → {"status":"done","result":{"score":92,"verdict":"CRITICAL", ... }}

# 3. (optional) fetch the screenshot
curl -s https://scan.stalkphish.io/api/scan/a1b2c3d4-.../screenshot -o shot.png

The edge only enqueues - it never fetches the target itself (SSRF containment). Defanged URLs are accepted.

Rate limits

Submitting is paced; reading a result is not. Your client is expected to sleep between submissions - the scanner refuses what arrives too fast rather than queueing it, so a batch sent all at once is mostly rejected, not merely delayed.

LimitValueApplies to
Minimum gap between submissions 5 s POST /api/scan, per IP
Submissions per window 1000 per hour POST /api/scan, per IP
Beta access code quota 300 per day POST /api/scan, per code
Feedback reports10 per hour POST /api/report, per IP
Reading resultsnot metered GET /api/scan/…

How to know where you stand

Every answer to POST /api/scan carries your current standing - on the 202 as much as on the 429. Read them and you never need to be refused:

HeaderMeaning
RateLimit-LimitSubmissions allowed in the window.
RateLimit-RemainingHow many you have left. Stop at 0.
RateLimit-ResetSeconds until the window refills.
RateLimit-PolicyThe window itself, e.g. 1000;w=3600.
Retry-AfterOn 429 and 503: seconds to wait before retrying.

Submitting a batch

One URL every 5 seconds. This is the whole recipe:

while read -r url; do
  curl -s -X POST https://scan.stalkphish.io/api/scan \
    -H 'Content-Type: application/json' \
    -H 'X-Access-Key: bk-yourcode' \
    -d "{\"url\":\"$url\"}"
  echo
  sleep 5
done < urls.txt

To watch your budget while you do it, ask curl for the headers:

curl -s -D - -o /dev/null -X POST https://scan.stalkphish.io/api/scan \
  -H 'Content-Type: application/json' \
  -H 'X-Access-Key: bk-yourcode' \
  -d '{"url":"https://suspicious-example.com/login"}' \
  | grep -i '^ratelimit-'

# RateLimit-Limit: 1000
# RateLimit-Remaining: 999
# RateLimit-Reset: 3599
# RateLimit-Policy: 1000;w=3600
StatusMeaningWhat to do
202Queued.Poll GET /api/scan/{uuid}.
429Too fast, or window spent. Sleep Retry-After seconds. Do not retry immediately.
503Scanner busy - the queue is full. Sleep Retry-After seconds. Nothing was charged to your quota.

A 429 from the edge is JSON ({"detail":"rate limit exceeded","retry_after":5}), so a script can read it without parsing HTML. Need a higher rate for a legitimate bulk job? Get in touch rather than working around the limit.

← Back to the scanner

Results come from automated tests, for information only and without warranty. They may be inaccurate and are not a definitive verdict - StalkPhish.io accepts no liability; always verify independently.