Overview

The HashScanner API puts the NIST NSRL online — 1.5 billion+ known files, queryable instantly without downloading the Reference Data Set. It’s a standard REST service over HTTPS: every request is authenticated with a Bearer token, responses are JSON, and outcomes are signalled with conventional HTTP status codes. A match means the file is cataloged in NSRL — not a safe or malicious verdict.

New to APIs? Start with the Quick start below — copy a snippet in your language, paste in your API key, and run it. Building at scale? Jump to Bulk lookups and Rate limits & quota.

Client libraries & integrations

Prefer not to call the REST endpoints directly? Drop HashScanner straight into your stack:

Python pip install hashscanner
  • Python client & CLIhashscanner on PyPI (single + bulk lookups; source on GitHub).
  • Cortex analyzer for TheHive/Cortex — look up hash observables in the NSRL from your SIRP: hashscanner-cortex.

Authentication

Every request must include your secret API key in the Authorization header as a Bearer token. Keys are generated automatically when you create an account and are shown in your member dashboard.

HTTP Header Authorization: Bearer hs_xxxxxxxxxx_sk_xxxxxxxxxxxxxxxxxxxx

An API key has two parts: a permanent key ID and a rotatable secret.

Key format hs_7f3k92m0qz_sk_41bb82a64ef6e4d253a106ebb7a6371e474562 └── key ID ──┘ └──────────────── secret ───────────────┘
  • Find your key in the dashboard under the API key widget after signing in.
  • You can regenerate the secret at any time. The old secret stops working immediately; the key ID stays the same.
  • Treat the key like a password — never commit it to source control or expose it in client-side code.

Requests without a valid key return 401 Unauthorized.

Base URL

All endpoints are served under the versioned base URL over HTTPS:

Base URL https://api.hashscanner.com/v1

Endpoints

GET
/v1/hash/{hash}
Look up a single hash. Best for realtime checks — results are edge-cached. Type auto-detected by length: MD5 (32), SHA-1 (40), or SHA-256 (64) hex chars.
POST
/v1/hash/bulk
Submit up to 100,000 hashes (in the JSON request body) as an asynchronous job. Returns a job ID immediately.
GET
/v1/hash/bulk/{job_id}
Check a bulk job's status. When complete, returns a time-limited URL to download the results file.

Need just a few hashes? Call the single endpoint concurrently. For large sets, submit a bulk job. The maximum hashes per job and your monthly total depend on your plan — see Rate limits & quota.

Quick start — single lookup

Replace hs_xxxxxxxxxx_sk_… with your own key and run. This looks up a single MD5 hash.

cURL curl https://api.hashscanner.com/v1/hash/d41d8cd98f00b204e9800998ecf8427e \ -H "Authorization: Bearer hs_xxxxxxxxxx_sk_xxxxxxxxxxxxxxxxxxxx"
Python import requests API_KEY = "hs_xxxxxxxxxx_sk_xxxxxxxxxxxxxxxxxxxx" hash_value = "d41d8cd98f00b204e9800998ecf8427e" resp = requests.get( f"https://api.hashscanner.com/v1/hash/{hash_value}", headers={"Authorization": f"Bearer {API_KEY}"}, ) resp.raise_for_status() print(resp.json())
PowerShell $ApiKey = "hs_xxxxxxxxxx_sk_xxxxxxxxxxxxxxxxxxxx" $Hash = "d41d8cd98f00b204e9800998ecf8427e" $response = Invoke-RestMethod ` -Uri "https://api.hashscanner.com/v1/hash/$Hash" ` -Headers @{ Authorization = "Bearer $ApiKey" } $response | ConvertTo-Json -Depth 5
PHP $apiKey = 'hs_xxxxxxxxxx_sk_xxxxxxxxxxxxxxxxxxxx'; $hash = 'd41d8cd98f00b204e9800998ecf8427e'; $ch = curl_init("https://api.hashscanner.com/v1/hash/$hash"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer $apiKey"], ]); $result = json_decode(curl_exec($ch), true); curl_close($ch); print_r($result);
Node.js (18+) const API_KEY = "hs_xxxxxxxxxx_sk_xxxxxxxxxxxxxxxxxxxx"; const hash = "d41d8cd98f00b204e9800998ecf8427e"; const res = await fetch(`https://api.hashscanner.com/v1/hash/${hash}`, { headers: { Authorization: `Bearer ${API_KEY}` }, }); const data = await res.json(); console.log(data);
Go package main import ( "fmt" "io" "net/http" ) func main() { apiKey := "hs_xxxxxxxxxx_sk_xxxxxxxxxxxxxxxxxxxx" hash := "d41d8cd98f00b204e9800998ecf8427e" req, _ := http.NewRequest("GET", "https://api.hashscanner.com/v1/hash/"+hash, nil) req.Header.Set("Authorization", "Bearer "+apiKey) resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) }
Java (11+) import java.net.URI; import java.net.http.*; public class Lookup { public static void main(String[] args) throws Exception { String apiKey = "hs_xxxxxxxxxx_sk_xxxxxxxxxxxxxxxxxxxx"; String hash = "d41d8cd98f00b204e9800998ecf8427e"; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.hashscanner.com/v1/hash/" + hash)) .header("Authorization", "Bearer " + apiKey) .GET() .build(); HttpResponse<String> response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); } }

Bulk lookup (async)

For large sets — up to 100,000 hashes — submit an asynchronous job. The flow has three steps:

  1. SubmitPOST /v1/hash/bulk with your hashes in the JSON body. A job_id is returned immediately (202 Accepted).
  2. PollGET /v1/hash/bulk/{job_id} every few seconds until status is completed (honour the Retry-After header).
  3. Download — fetch the results_url from the completed response to stream the results file (NDJSON or CSV).

Each hash counts as one lookup against your monthly quota (charged when the job is accepted). The per-job maximum and your monthly total depend on your plan. The snippet below runs the full submit → poll → download flow.

cURL # 1. Submit the job — hashes go in the request body curl -X POST https://api.hashscanner.com/v1/hash/bulk \ -H "Authorization: Bearer hs_xxxxxxxxxx_sk_xxxxxxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{"hashes":["d41d8cd98f00b204e9800998ecf8427e","da39a3ee5e6b4b0d3255bfef95601890afd80709"],"format":"json"}' # → {"job_id":"job_a1b2c3d4e5","status":"queued","total":2, ...} # 2. Poll until status is "completed" curl https://api.hashscanner.com/v1/hash/bulk/job_a1b2c3d4e5 \ -H "Authorization: Bearer hs_xxxxxxxxxx_sk_xxxxxxxxxxxxxxxxxxxx" # → {"status":"completed","results_url":"https://.../results?token=...", ...} # 3. Download the results file (NDJSON) curl -o results.ndjson "https://api.hashscanner.com/v1/hash/bulk/job_a1b2c3d4e5/results?token=..."
Python import time, requests API = "https://api.hashscanner.com/v1" HEADERS = {"Authorization": "Bearer hs_xxxxxxxxxx_sk_xxxxxxxxxxxxxxxxxxxx"} hashes = [ "d41d8cd98f00b204e9800998ecf8427e", "da39a3ee5e6b4b0d3255bfef95601890afd80709", ] # 1. Submit job = requests.post(f"{API}/hash/bulk", headers=HEADERS, json={"hashes": hashes, "format": "json"}).json() job_id = job["job_id"] # 2. Poll until complete while True: status = requests.get(f"{API}/hash/bulk/{job_id}", headers=HEADERS).json() if status["status"] in ("completed", "failed"): break time.sleep(3) # 3. Stream and parse the NDJSON results if status["status"] == "completed": results = requests.get(status["results_url"]) for line in results.iter_lines(): if line: print(line.decode())
PowerShell $Api = "https://api.hashscanner.com/v1" $Headers = @{ Authorization = "Bearer hs_xxxxxxxxxx_sk_xxxxxxxxxxxxxxxxxxxx" } $Body = @{ hashes = @( "d41d8cd98f00b204e9800998ecf8427e", "da39a3ee5e6b4b0d3255bfef95601890afd80709" ) format = "json" } | ConvertTo-Json # 1. Submit $job = Invoke-RestMethod -Method Post -Uri "$Api/hash/bulk" ` -Headers $Headers -ContentType "application/json" -Body $Body # 2. Poll until complete do { Start-Sleep -Seconds 3 $status = Invoke-RestMethod -Uri "$Api/hash/bulk/$($job.job_id)" -Headers $Headers } while ($status.status -eq "queued" -or $status.status -eq "processing") # 3. Download the results file if ($status.status -eq "completed") { Invoke-WebRequest -Uri $status.results_url -OutFile "results.ndjson" }
PHP $api = 'https://api.hashscanner.com/v1'; $auth = ['Authorization: Bearer hs_xxxxxxxxxx_sk_xxxxxxxxxxxxxxxxxxxx']; function api_call($method, $url, $headers, $body = null) { $ch = curl_init($url); $opts = [CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => $method]; if ($body !== null) { $opts[CURLOPT_POSTFIELDS] = $body; $headers[] = 'Content-Type: application/json'; } $opts[CURLOPT_HTTPHEADER] = $headers; curl_setopt_array($ch, $opts); $out = curl_exec($ch); curl_close($ch); return $out; } // 1. Submit $payload = json_encode(['hashes' => [ 'd41d8cd98f00b204e9800998ecf8427e', 'da39a3ee5e6b4b0d3255bfef95601890afd80709', ], 'format' => 'json']); $job = json_decode(api_call('POST', "$api/hash/bulk", $auth, $payload), true); // 2. Poll until complete do { sleep(3); $status = json_decode(api_call('GET', "$api/hash/bulk/{$job['job_id']}", $auth), true); } while (in_array($status['status'], ['queued', 'processing'])); // 3. Download the results file if ($status['status'] === 'completed') { file_put_contents('results.ndjson', file_get_contents($status['results_url'])); }
Node.js (18+) const API = "https://api.hashscanner.com/v1"; const headers = { Authorization: "Bearer hs_xxxxxxxxxx_sk_xxxxxxxxxxxxxxxxxxxx" }; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); // 1. Submit const submit = await fetch(`${API}/hash/bulk`, { method: "POST", headers: { ...headers, "Content-Type": "application/json" }, body: JSON.stringify({ hashes: [ "d41d8cd98f00b204e9800998ecf8427e", "da39a3ee5e6b4b0d3255bfef95601890afd80709", ], format: "json", }), }); const { job_id } = await submit.json(); // 2. Poll until complete let status; do { await sleep(3000); status = await (await fetch(`${API}/hash/bulk/${job_id}`, { headers })).json(); } while (status.status === "queued" || status.status === "processing"); // 3. Stream and parse the NDJSON results if (status.status === "completed") { const text = await (await fetch(status.results_url)).text(); text.trim().split("\n").forEach((line) => console.log(JSON.parse(line))); }
Go package main import ( "bytes" "encoding/json" "fmt" "io" "net/http" "os" "time" ) const ( api = "https://api.hashscanner.com/v1" apiKey = "hs_xxxxxxxxxx_sk_xxxxxxxxxxxxxxxxxxxx" ) func call(method, url string, body io.Reader) []byte { req, _ := http.NewRequest(method, url, body) req.Header.Set("Authorization", "Bearer "+apiKey) if body != nil { req.Header.Set("Content-Type", "application/json") } resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() out, _ := io.ReadAll(resp.Body) return out } func main() { // 1. Submit payload, _ := json.Marshal(map[string]any{ "hashes": []string{ "d41d8cd98f00b204e9800998ecf8427e", "da39a3ee5e6b4b0d3255bfef95601890afd80709", }, "format": "json", }) var job struct{ JobID string `json:"job_id"` } json.Unmarshal(call("POST", api+"/hash/bulk", bytes.NewReader(payload)), &job) // 2. Poll until complete var status struct { Status string `json:"status"` ResultsURL string `json:"results_url"` } for { time.Sleep(3 * time.Second) json.Unmarshal(call("GET", api+"/hash/bulk/"+job.JobID, nil), &status) if status.Status == "completed" || status.Status == "failed" { break } } // 3. Download the results file if status.Status == "completed" { os.Stdout.Write(call("GET", status.ResultsURL, nil)) } }
Java (11+) import java.net.URI; import java.net.http.*; import java.util.regex.*; public class BulkLookup { static final String API = "https://api.hashscanner.com/v1"; static final String KEY = "hs_xxxxxxxxxx_sk_xxxxxxxxxxxxxxxxxxxx"; static final HttpClient HTTP = HttpClient.newHttpClient(); // Minimal field extractor — use Jackson/Gson for real JSON parsing. static String field(String json, String name) { Matcher m = Pattern.compile("\"" + name + "\"\\s*:\\s*\"([^\"]*)\"").matcher(json); return m.find() ? m.group(1) : null; } static String send(HttpRequest req) throws Exception { return HTTP.send(req, HttpResponse.BodyHandlers.ofString()).body(); } public static void main(String[] args) throws Exception { // 1. Submit String body = "{\"hashes\":[\"d41d8cd98f00b204e9800998ecf8427e\"," + "\"da39a3ee5e6b4b0d3255bfef95601890afd80709\"],\"format\":\"json\"}"; String jobJson = send(HttpRequest.newBuilder() .uri(URI.create(API + "/hash/bulk")) .header("Authorization", "Bearer " + KEY) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(body)).build()); String jobId = field(jobJson, "job_id"); // 2. Poll until complete String status, statusJson; do { Thread.sleep(3000); statusJson = send(HttpRequest.newBuilder() .uri(URI.create(API + "/hash/bulk/" + jobId)) .header("Authorization", "Bearer " + KEY).GET().build()); status = field(statusJson, "status"); } while ("queued".equals(status) || "processing".equals(status)); // 3. Download the results file if ("completed".equals(status)) { System.out.println(send(HttpRequest.newBuilder() .uri(URI.create(field(statusJson, "results_url"))).GET().build())); } } }

Response

The single-hash endpoint returns one object. The bulk flow returns a job envelope at submission, a status object while polling, and a downloadable results file on completion. The data object is present only when found is true.

Single hash

JSON { "found": true, "hash": "d41d8cd98f00b204e9800998ecf8427e", "type": "md5", "data": { "reputation": { "status": "known", "source": "NSRL" }, "file": { "file_name": "shell32.dll", "file_size": 1284096 }, "package": { "name": "Windows 10 Pro" }, "operating_system": { "name": "Windows" }, "manufacturer": "Microsoft Corporation", "language": "English", "application_type": "Operating System", "version": { "description": "NSRL generated full RDSv3 database build" } } }

If the hash is not in the dataset, the API returns 404 with { "found": false }.

Bulk job — submit (202 Accepted)

JSON { "job_id": "job_a1b2c3d4e5", "status": "queued", "total": 7500, "status_url": "https://api.hashscanner.com/v1/hash/bulk/job_a1b2c3d4e5" }

Bulk job — polling

JSON — processing { "job_id": "job_a1b2c3d4e5", "status": "processing", "total": 7500, "processed": 3200 }
JSON — completed { "job_id": "job_a1b2c3d4e5", "status": "completed", "total": 7500, "found": 6100, "not_found": 1400, "format": "json", "results_url": "https://api.hashscanner.com/v1/hash/bulk/job_a1b2c3d4e5/results?token=...", "expires_at": "2026-06-09T00:00:00Z" }

A failed job returns { "status": "failed", "error": "..." }.

Bulk job — results file

Download results_url to stream the file. NDJSON (default) is one JSON record per line — ideal for streaming large sets:

NDJSON {"hash":"d41d8cd98f00b204e9800998ecf8427e","type":"md5","found":true,"data":{"reputation":{"status":"known","source":"NSRL"},"file":{"file_name":"shell32.dll","file_size":1284096}}} {"hash":"da39a3ee5e6b4b0d3255bfef95601890afd80709","type":"sha1","found":false}

Request "format": "csv" at submission to get a spreadsheet-friendly file instead:

CSV hash,type,found,file_name,file_size,product,os,source d41d8cd98f00b204e9800998ecf8427e,md5,true,shell32.dll,1284096,Windows 10 Pro,Windows,NSRL da39a3ee5e6b4b0d3255bfef95601890afd80709,sha1,false,,,,,

Fields

FieldTypeDescription
foundbooleanWhether the hash exists in the dataset.
hashstringThe queried hash (single-hash response).
typestringDetected hash type: md5, sha1, or sha256.
dataobjectMetadata for the file. Present only when found is true.
data.reputation.statusstringReputation classification (e.g. known).
data.reputation.sourcestringIntelligence source (e.g. NSRL).
data.file.file_namestringOriginal file name.
data.file.file_sizeintegerFile size in bytes.
data.package.namestringSoftware package the file ships with.
data.operating_system.namestringAssociated operating system.
data.manufacturerstringSoftware vendor / manufacturer.
data.languagestringFile or product language.
data.application_typestringFile category (e.g. Operating System).
data.version.descriptionstringDataset build description.

Available metadata fields vary by record — fields without a value for a given file are omitted.

Bulk job fields

FieldTypeDescription
job_idstringUnique identifier for the bulk job.
statusstringJob state: queued, processing, completed, or failed.
totalintegerNumber of hashes accepted for the job.
processedintegerHashes processed so far (while processing).
found / not_foundintegerCounts at completion.
formatstringResult file format: json (NDJSON) or csv.
results_urlstringTime-limited URL to download the results file.
expires_atstringISO-8601 timestamp when the results file is purged.

Errors & status codes

The API uses standard HTTP status codes. Error responses carry a JSON body with an error message.

StatusMeaning
200 OKRequest succeeded (single lookup, or bulk job poll).
202 AcceptedBulk job created — poll the status_url for progress.
400 Bad RequestMalformed body, invalid hash, or more than 100,000 hashes in one bulk job.
401 UnauthorizedAPI key missing, malformed, unknown, or invalid.
403 ForbiddenSubscription inactive — renew or upgrade to continue.
404 Not FoundSingle lookup: hash not in the dataset ({ "found": false }). Bulk: unknown or expired job ID. Also returned for an unrecognised path.
405 Method Not AllowedWrong HTTP method for the endpoint (e.g. POST to a lookup path).
429 Too Many RequestsPer-minute rate limit or monthly quota exceeded.

Rate limits & quota

Three limits apply, all determined by your plan:

  • Monthly quota — total hash lookups per billing period. Each hash counts as one, so a bulk job of N hashes consumes N (charged when the job is accepted).
  • Rate limit — maximum API requests per minute.
  • Bulk job size — maximum hashes per bulk job, up to 100,000 (plan-dependent).

Exceeding any of these returns 429 Too Many Requests. Rate-limit headers are included on responses:

Response headers X-RateLimit-Remaining: 4750 X-RateLimit-Reset: 2026-07-01T00:00:00Z

Completed bulk results files are retained for about 72 hours, then purged — download them promptly. Track your live usage, remaining quota, and reset date in your member dashboard, and compare per-plan limits on the pricing page.

Get your API key

Sign up for a free account and start integrating in minutes.