Anti-Malware — entry 013 of 15
URLScan.io
urlscan.io submits URLs to a real, sandboxed browser and publishes the resulting screenshot, DOM, requested domains, and detected technologies, plus a searchable archive of past scans, all as JSON. A live unauthenticated GET against its search endpoint succeeded immediately (returning IP-scoped X-Rate-Limit headers), and a follow-up OPTIONS preflight returned Access-Control-Allow-Origin: *, resolving cors to yes and confirming HTTPS; submitting new scans instead requires an API-Key header. Public scanning is free within per-minute/hour/day rate limits, with a paid urlscan Pro tier for vetted researchers needing deeper private search.
urlscan.io scans a URL in a real sandboxed browser and stores the result: a screenshot, the page's DOM, every domain it contacted, and the technologies it found. Most of the API needs an API key. One part does not: the search endpoint. A live GET this run against /search/ returned real results with no credential sent.
GreatAPIs Score
Auth quickstart
- Search needs no key.
GET /search/?q=<query>works with noapi-keyheader. A live call this run returnedHTTP 200with real scan results. - Submitting a new scan needs a key. A live
POST /scan/this run with no key returnedHTTP 401:No API key supplied. Please supply a valid API key in the "api-key" HTTP header. - Reading a single scan result by UUID also needs a key, even for a public scan. A live
GET /result/<uuid>/this run returnedHTTP 403:You're not logged in! - Get a free API key by creating an account at urlscan.io, then send it as an
api-keyheader on/scan/and/result/calls.
Your key is stored only in this browser (localStorage) and sent directly to the API — never to greatapis.
Keyless search for past scans of a domain
GEThttps://urlscan.io/api/v1/search/?q=page.domain:github.com&size=2
{"results":[{"task":{"visibility":"public","method":"api","domain":"github.com","time":"2026-08-22T08:11:33.895Z","uuid":"01a02886-515a-7018-bfb9-79a4ab620ebd","url":"https://github.com/2dust/v2rayN"},"stats":{"uniqIPs":5,"uniqCountries":2,"requests":172},"page":{"country":"DE","server":"github.com","ip":"140.82.121.4","status":"200"},"result":"https://urlscan.io/api/v1/result/01a02886-515a-7018-bfb9-79a4ab620ebd/","screenshot":"https://urlscan.io/screenshots/01a02886-515a-7018-bfb9-79a4ab620ebd.png"}],"total":10000,"took":50,"has_more":true}The q field name matters. A live query with q=domain:github.com this run returned unrelated hosts, and q=page.domain:github.com returned real github.com results. urlscan does not reject an unknown field name; it just ignores it and returns the wrong rows.
Developer reference
30 requests/minute, 100/hour, 500/day for the "search" action -- default anonymous/IP-scoped tier (public submission scan: 30/min, 250/hour, 2,500/day; private scan: 5/min, 25/hour, 25/day) -- per urlscan.io/user/quotas/
Gotchas & limits
- Search is the only keyless part of this API. A live
GET /result/<uuid>/this run, even for a public scan, returnedHTTP 403 {"warning": "You're not logged in!"}. Use theresultandscreenshotURLs from a search hit directly in a browser instead of calling/result/from a script with no key. - The unauthenticated tier is rate-limited by IP, not by key. A live search call this run returned
x-rate-limit-limit: 30andx-rate-limit-window: minute, plusx-rate-limit-concurrency-limit: 10for how many requests can run at once. - CORS is wide open. A live
OPTIONSpreflight against/search/this run returnedHTTP 200withaccess-control-allow-origin: *, so a browser page can call search directly with no server proxy. totalis a ceiling, not a result count. Three separate high-volume queries this run (page.domain:github.com,domain:github.com,page.domain:google.com) each returnedtotal: 10000, and a query with no matches returnedtotal: 0. Treat10000as "at least this many" and page withhas_moreinstead of trustingtotalas a real total.