VirusTotal scans files and URLs against 70+ antivirus engines simultaneously. Their API is free for up to 500 requests per day.
One scan. Seventy verdicts. Zero cost.
The Story
A friend downloaded a tool from GitHub. Their antivirus said it was clean. But they had a nagging feeling — one engine isn't enough.
They uploaded it to VirusTotal manually. Three engines flagged it as a trojan. The "clean" binary was actually malware that their specific AV didn't detect.
Now they automate this check for every download. Here's how.
Get Your Free API Key
- Sign up at virustotal.com
- Go to your profile → API Key
- Copy your key (free tier: 500 lookups/day, 4 requests/minute)
Scan a URL
\`python
import requests
import time
API_KEY = "your-api-key-here"
headers = {"x-apikey": API_KEY}
Submit URL for scanning
url_to_scan = "https://example.com/suspicious-download"
response = requests.post(
"https://www.virustotal.com/api/v3/urls",
headers=headers,
data={"url": url_to_scan}
)
analysis_id = response.json()["data"]["id"]
print(f"Scan submitted: {analysis_id}")
Wait for results
time.sleep(15)
Get results
result = requests.get(
f"https://www.virustotal.com/api/v3/analyses/{analysis_id}",
headers=headers
).json()
stats = result["data"]["attributes"]["stats"]
print(f"Malicious: {stats['malicious']}")
print(f"Suspicious: {stats['suspicious']}")
print(f"Clean: {stats['undetected']}")
print(f"Timeout: {stats['timeout']}")
`\
Check a File Hash
Don't upload the file — just check its hash. Faster and more private:
\`python
import hashlib
def check_file(filepath):
"""Check if a file is known malware by its SHA-256 hash."""
# Calculate hash
sha256 = hashlib.sha256()
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
sha256.update(chunk)
file_hash = sha256.hexdigest()
# Query VirusTotal
response = requests.get(
f"https://www.virustotal.com/api/v3/files/{file_hash}",
headers={"x-apikey": API_KEY}
)
if response.status_code == 200:
attrs = response.json()["data"]["attributes"]
stats = attrs["last_analysis_stats"]
print(f"File: {filepath}")
print(f"SHA-256: {file_hash}")
print(f"Detection: {stats['malicious']}/{sum(stats.values())} engines")
print(f"First seen: {attrs.get('first_submission_date', 'Unknown')}")
if stats["malicious"] > 0:
print("⚠ MALWARE DETECTED!")
# Show which engines flagged it
for engine, result in attrs["last_analysis_results"].items():
if result["category"] == "malicious":
print(f" └─ {engine}: {result['result']}")
else:
print("✓ File appears clean")
elif response.status_code == 404:
print(f"File not in VirusTotal database (never scanned)")
check_file("suspicious_file.exe")
`\
Scan a Domain
Check if a domain is associated with malware:
\`python
def check_domain(domain):
"""Check domain reputation on VirusTotal."""
response = requests.get(
f"https://www.virustotal.com/api/v3/domains/{domain}",
headers={"x-apikey": API_KEY}
)
if response.status_code == 200:
attrs = response.json()["data"]["attributes"]
stats = attrs.get("last_analysis_stats", {})
print(f"Domain: {domain}")
print(f"Malicious: {stats.get('malicious', 0)}")
print(f"Suspicious: {stats.get('suspicious', 0)}")
print(f"Clean: {stats.get('undetected', 0)}")
# WHOIS data
whois = attrs.get("whois", "Not available")
print(f"Registrar: {attrs.get('registrar', 'Unknown')}")
print(f"Created: {attrs.get('creation_date', 'Unknown')}")
check_domain("example.com")
`\
Batch Scanner — Check Multiple IOCs
\`python
import time
def batch_scan(iocs, ioc_type="url"):
"""Scan multiple indicators of compromise."""
results = []
for ioc in iocs:
if ioc_type == "url":
endpoint = f"https://www.virustotal.com/api/v3/urls"
# URL ID is base64url of the URL
import base64
url_id = base64.urlsafe_b64encode(ioc.encode()).decode().strip("=")
r = requests.get(
f"https://www.virustotal.com/api/v3/urls/{url_id}",
headers={"x-apikey": API_KEY}
)
elif ioc_type == "hash":
r = requests.get(
f"https://www.virustotal.com/api/v3/files/{ioc}",
headers={"x-apikey": API_KEY}
)
elif ioc_type == "ip":
r = requests.get(
f"https://www.virustotal.com/api/v3/ip_addresses/{ioc}",
headers={"x-apikey": API_KEY}
)
if r.status_code == 200:
stats = r.json()["data"]["attributes"].get("last_analysis_stats", {})
mal = stats.get("malicious", 0)
results.append({"ioc": ioc, "malicious": mal, "status": "⚠ THREAT" if mal > 0 else "✓ Clean"})
else:
results.append({"ioc": ioc, "malicious": 0, "status": "? Not found"})
time.sleep(15) # Free tier rate limit
for r in results:
print(f"{r['status']} | {r['ioc']} | {r['malicious']} detections")
Check suspicious IPs
batch_scan(["8.8.8.8", "1.1.1.1"], ioc_type="ip")
`\
What You Can Build
- Download scanner — automatically check every file before opening
- Email security gateway — scan attachments and links
- Incident response tool — bulk-check IOCs from threat reports
- Browser extension backend — warn users about malicious URLs
- CI/CD security check — scan build artifacts before deployment
Free Tier Limits
| Feature | Free | Premium |
|---|---|---|
| Lookups/day | 500 | 30,000+ |
| Requests/minute | 4 | 720 |
| File upload | 32 MB | 650 MB |
| Hunting rules | No | Yes |
500 lookups per day is generous for personal security tools and small projects.
Building security tools? Check my GitHub for more free API tutorials and developer tools.
Top comments (0)