Scan files for malware, right from your code
Add antivirus scanning to your uploads, pipelines, and workflows with a single HTTP request. No infrastructure to run, no files stored on our servers.
API access is included with every Premium plan.
Quick start
Swap in your key and run — no SDK required
API_KEY="vp_<your-api-key>"
# 1. Ask for an upload permit (the file itself never goes to this API)
RESP=$(curl -s https://viruspurge.com/api/v1/scan \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"fileName":"file.pdf","fileSize":'$(wc -c < file.pdf)'}')
# 2. Upload the file straight to the scanner — the response is the verdict
curl -s "$(echo "$RESP" | jq -r .uploadUrl)" \
-H "X-Scan-Permit: $(echo "$RESP" | jq -r .permit)" \
-F "[email protected]"
# 3. Any time later — or if the upload connection dropped — read it back
curl -s "https://viruspurge.com/api/v1/scan/$(echo "$RESP" | jq -r .scanId)" \
-H "Authorization: Bearer $API_KEY"import os, time, httpx
API = "https://viruspurge.com/api/v1"
HEADERS = {"Authorization": "Bearer vp_<your-api-key>"}
path = "file.pdf"
# 1. Permit
permit = httpx.post(f"{API}/scan", headers=HEADERS, json={
"fileName": os.path.basename(path),
"fileSize": os.path.getsize(path),
}).raise_for_status().json()
# 2. Upload straight to the scanner
try:
with open(path, "rb") as f:
verdict = httpx.post(permit["uploadUrl"], files={"file": f},
headers={"X-Scan-Permit": permit["permit"]},
timeout=300).raise_for_status().json()
except httpx.TransportError:
# 3. Connection lost: the verdict is still recorded under the scan id
while (verdict := httpx.get(f"{API}/scan/{permit['scanId']}",
headers=HEADERS).json())["status"] == "SCANNING":
time.sleep(2)
print(verdict["isInfected"], verdict["viruses"])import { openAsBlob, statSync } from "node:fs";
const API = "https://viruspurge.com/api/v1";
const headers = { Authorization: "Bearer vp_<your-api-key>" };
const path = "file.pdf";
// 1. Permit
const permit = await fetch(`${API}/scan`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ fileName: path, fileSize: statSync(path).size }),
}).then((r) => r.json());
// 2. Upload straight to the scanner (Node 20+: no dependencies)
const form = new FormData();
form.append("file", await openAsBlob(path), path);
const verdict = await fetch(permit.uploadUrl, {
method: "POST",
headers: { "X-Scan-Permit": permit.permit },
body: form,
}).then((r) => r.json());
// 3. Or read it back later: GET `${API}/scan/${permit.scanId}`
console.log(verdict.isInfected, verdict.viruses);# Node 20+. Scans files, directories and globs; exits 1 if malware is found.
export VIRUSPURGE_API_KEY="vp_<your-api-key>"
npx viruspurge scan dist/ "build/**/*.{zip,exe,dmg}"
# Machine-readable report, e.g. to archive with a release
npx viruspurge scan dist/ --json --report scan-report.json
# Look a scan up again by id
npx viruspurge status <scanId># .github/workflows/scan-release.yml
# Add your API key as the repository secret VIRUSPURGE_API_KEY.
name: Scan release artifacts
on:
release:
types: [published]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- run: gh release download "${{ github.event.release.tag_name }}" -D dist -R "$GITHUB_REPOSITORY"
env:
GH_TOKEN: ${{ github.token }}
- uses: decentralisoft/virus-scanner-cicd-action@v1
with:
api-key: ${{ secrets.VIRUSPURGE_API_KEY }}
files: dist/**Zero-upload architecture
Files never touch our app server. The API issues a short-lived signed permit and your client uploads straight to the scanner.
Real-time detection
Every request is scanned against a continuously-updated signature and YARA ruleset, and every image is checked for hidden payloads, returning the verdict and any matched threats.
One endpoint, any language
A single POST request with a Bearer token. Copy-paste cURL, Python, or Node.js and you're scanning in under a minute.
Built for scale
Files up to 256 MB, 300 scans per day, and 60 scans per minute — with signed results pushed back into your scan history.
The endpoint
- Authenticate with
Authorization: Bearer <api-key> - Send the file as
multipart/form-dataunder thefilefield
Example response
Ready to start scanning?
Upgrade to Premium to generate your personal API key and start scanning programmatically today.