VirusTotal aggregates 70+ anti-malware engines, multiple sandboxes, and a massive crowd-sourced corpus of files, URLs, domains and IPs. The web UI is fine for a one-off check, but a SOC that triages hundreds of indicators a day lives in the API. This guide takes you from a fresh API key to a production enrichment pipeline using the current v3 API.
You will install both official clients — vt-cli and vt-py — query every object type, build a Dockerised enrichment microservice, run bulk hunting with Retrohunt and Livehunt, and wire verdicts into Microsoft Sentinel and Splunk. Every command is copy-pasteable and uses non-sensitive demo indicators (the EICAR test file, example.com, 8.8.8.8) so you can follow along on the free tier.
◈ Table of Contents
01 — WHAT THE VIRUSTOTAL API IS & WHY A DEFENDER CARES
BEGINNERVirusTotal (VT) is a Google/Chronicle-owned threat-intelligence service. When you submit a file hash, URL, domain or IP, VT returns the combined verdict of 70+ anti-malware engines plus sandbox behaviour, static properties, and a web of relationships — the other domains a file contacted, the files that dropped it, the IPs a domain resolved to. For a SOC analyst, that turns a lone, context-free indicator into an attributed story.
The modern interface is the v3 REST API. Every request is plain HTTPS with a single authentication header, x-apikey, and every response is a JSON object — a file, url, domain, ip_address or analysis. Learn the object model once and every endpoint behaves the same way. The legacy v2 API still answers but is deprecated; write all new tooling against v3.
Alert Enrichment
Auto-attach a VT verdict to every file hash, URL and IP in a SIEM alert so analysts triage with context instead of a bare indicator.
IOC Validation
Confirm whether an indicator from a threat feed is still live, widely detected, or a false positive before you block it fleet-wide.
Pivoting
Follow relationships from one dropper to its C2 domains, sibling samples and campaign infrastructure without ever leaving the terminal.
Retro & Live Hunting
Run a YARA rule across the historic corpus (Retrohunt) or against every new submission going forward (Livehunt) to catch your threat early.
Bulk Triage
Score a CSV of hundreds of quarantined hashes overnight and surface only the ones worth an analyst's attention.
Surface Monitoring
Watch for your own files, domains or brand appearing in the public corpus — an early signal of a leak or impersonation campaign.
VT reputation is an input, not a verdict. A clean VT result does not prove a file is safe — targeted malware is often unknown to VT. Treat "0/72 detections" as "no community evidence yet", never as "benign".
Every v3 endpoint returns an object with a stable shape: a type, an id, a bag of attributes, and a set of relationships you can expand. Once you can read one object, you can read them all.
| Object | Endpoint (GET) | ID is… |
|---|---|---|
| file | /api/v3/files/{id} | MD5, SHA-1 or SHA-256 |
| url | /api/v3/urls/{id} | base64url(url) without padding, or SHA-256 of the URL |
| domain | /api/v3/domains/{id} | the domain name |
| ip_address | /api/v3/ip_addresses/{id} | the IPv4/IPv6 address |
| analysis | /api/v3/analyses/{id} | ID returned when you submit a file/URL |
The single most useful attribute on a file, domain or ip_address object is last_analysis_stats — the counts of malicious, suspicious, undetected, harmless and timeout engines. That ratio (for example 52/72 malicious) is the number your automation keys off.
02 — PREREQUISITES & API KEY TIERS
BEGINNERCreate a free account at virustotal.com, then open your profile menu and choose API key. The key is a 64-character hex string tied to your account. Everything in this guide works on the free (Public) key; the premium tiers remove the rate limits and unlock Intelligence search, Retrohunt and Livehunt.
Store the key in an environment variable so it never lands in your shell history or a committed script. On Linux/macOS:
Never hardcode the key in source, notebooks or container images, and never commit ~/.vt.toml. A leaked VT key can be abused against your quota and, on premium, exposes your Intelligence searches. Rotate it from your profile the moment it appears in any shared location.
The free Public API is rate-limited and non-commercial. Design your tooling around the ceiling from day one — it is the single most common thing that breaks a VT script in production.
| Capability | Public (Free) | Premium / Enterprise |
|---|---|---|
| Request rate | 4 requests / minute | Contract-defined, far higher |
| Daily quota | 500 requests / day | Contract-defined |
| Lookups (hash/url/domain/ip) | Yes | Yes |
| Upload & scan files | Yes | Yes |
| Intelligence search | No | Yes |
| Retrohunt | No | Yes |
| Livehunt (YARA) | No | Yes |
| Commercial use | Prohibited | Permitted under contract |
The free API is for research and non-commercial use only, and may not be built into a product or a workflow that does not contribute new files. Using a free key in a commercial SOC pipeline violates VT's terms — use a licensed Premium key there.
At 4 req/min a naive loop over 500 hashes takes two-plus hours and then dies at the daily cap. Batch, cache verdicts locally, and sleep 15 s between calls. The automation section builds this in.
Pick whichever client matches your workflow. You do not need all three — but seeing the same lookup in CLI, Python and raw REST makes the object model click.
| Component | Version | Why |
|---|---|---|
| vt-cli | latest release recommended | Fast one-liners & shell pipelines |
| Python | 3.8+ min | Runs the vt-py library |
| vt-py | latest (pip) | Programmatic enrichment, async scanning |
| curl / jq | any recent | Raw REST testing & JSON parsing |
| Docker + Compose | 24+ / v2 | Containerised enrichment microservice |
03 — METHOD 1: THE vt-cli COMMAND LINE
BEGINNERvt-cli is VirusTotal's official Go-based command-line client. It is a single static binary with no runtime dependencies, which makes it ideal for analyst workstations and CI runners alike.
The exact release asset name (Linux64.zip, macOS64.zip, windows64.zip) changes between versions. Always copy the link for the latest release from the GitHub releases page rather than assuming a filename.
Either run the interactive initialiser — which writes the key to ~/.vt.toml — or rely on the VTCLI_APIKEY environment variable you set earlier. The --apikey flag overrides both for one-off calls.
Add vt completion to your shell with vt completion bash (or zsh/fish). Tab-completing object types and flags saves a surprising amount of friction during live triage.
Full objects are huge. Use --include to pull only the attributes you need and --format json when you intend to pipe into jq. This also conserves nothing on quota (one lookup is one lookup) but it keeps your terminal readable.
04 — METHOD 2: THE vt-py PYTHON LIBRARY
INTERMEDIATEvt-py is VirusTotal's official Python client. Always install it into a project virtual environment so its dependency pins never collide with your system Python.
The client reads objects with get_object() using the same path you would hit over REST. Read the key off the environment — never inline it.
Accessing an attribute that is absent raises AttributeError. Use f.get("reputation") for fields that may not exist (new or rarely-seen objects) instead of dotted access.
On premium quota you will want concurrency. vt.Client doubles as an async context manager, exposing *_async() coroutines you can gather. (On the free tier the 4/min limit dominates, so keep it serial there.)
05 — METHOD 3: RAW REST & A DOCKERISED SERVICE
INTERMEDIATEAny HTTP client works — the only requirement is the x-apikey header. This is what you reach for when debugging, or when integrating from a language without an official SDK.
A small enrichment microservice is the cleanest way to share VT access across a team without handing the key to everyone. Here is a complete Flask + vt-py service with a docker-compose.yml that keeps the key in an env file and caps the container.
Bind the service to 127.0.0.1 (or put it behind an authenticated reverse proxy) and never expose it to the internet. An open enrichment endpoint lets anyone spend your VT quota and, on premium, run Intelligence queries on your dime.
- vt version, python -c "import vt", and a curl file lookup all succeed
- VT_API_KEY resolves from the environment, not a hardcoded string
- ~/.vt.toml and .env are chmod 600 and git-ignored
- the containerised endpoint returns a verdict for the EICAR hash
06 — FIRST LOOKUPS: FILES, URLS, DOMAINS & IPS
BEGINNERThe number everyone quotes — "52/72" — is last_analysis_stats.malicious over the total engines. But a serious triage reads more: first/last submission dates tell you whether this is a fresh sample or a years-old commodity, and reputation plus community votes flag known false positives.
| Attribute | What it tells you |
|---|---|
| last_analysis_stats | malicious / suspicious / undetected / harmless engine counts |
| meaningful_name | the most representative filename seen in the wild |
| type_description | file type (PE32 executable, PDF, Office Open XML…) |
| first_submission_date | epoch of first time VT ever saw this file |
| times_submitted | how widespread/commodity the sample is |
| reputation | community score; strongly negative = widely flagged |
| signature_info | Authenticode signer, if the binary is signed |
One or two "malicious" engines on a widely-submitted, old, signed file is usually a false positive. Three-plus independent, reputable engines on a rarely-seen, recently-submitted binary is the pattern that warrants escalation.
For network indicators, the engine verdict matters less than the context: who owns the ASN, when the domain was registered (a day-old domain is a red flag), the current resolutions, and the HTTPS certificate. VT surfaces all of it on the object.
If a URL has never been seen, submit it for analysis. VT returns an analysis ID you poll until it is complete, then read the resulting URL object. vt-py wraps the whole dance in one call.
07 — CORE WORKFLOWS: SCAN, POLL & PIVOT
INTERMEDIATETo analyse a file VT has never seen, upload it. vt-py streams the file and, with wait_for_completion=True, blocks until the multi-engine scan and sandbox run finish. Files over 32 MB need a special upload URL, which vt-py negotiates for you.
Uploading a file makes it available to VT and, on the premium corpus, to other researchers. Never upload documents containing secrets, PII, or anything that identifies your organisation — a leaked internal file in VT is a disclosure incident. For sensitive samples, look up the hash first; only upload if you accept it going public.
When you do not want to block, submit and poll. The analysis object carries a status of queued, in-progress or completed. Respect the rate limit while polling.
Relationships are what make VT an investigation tool rather than a lookup table. From a single dropper you can walk to the domains it contacted, the files it wrote, and the parent that delivered it — building out the campaign without touching the endpoint again.
| Object | Useful relationship | Reveals |
|---|---|---|
| file | contacted_domains / contacted_ips | C2 and callback infrastructure |
| file | dropped_files / execution_parents | payload chain & delivery vector |
| domain | resolutions / communicating_files | hosting IPs & malware using the domain |
| ip_address | communicating_files / urls | everything that talked to the host |
Over REST you can expand relationships inline with ?relationships=contacted_domains,dropped_files on the file endpoint — one request instead of three, which matters on the 4/min free limit.
08 — ADVANCED: INTELLIGENCE, RETROHUNT & LIVEHUNT
ADVANCEDIntelligence search (premium) lets you query the entire corpus with modifiers — file type, detection count, sandbox behaviour, import hash, submitter geography and dozens more. It is the difference between "tell me about this hash" and "show me every PE in the last week that beacons and is signed by this cert".
Build searches from the results of an incident: take the imphash or a distinctive mutex from one confirmed sample and search the corpus for siblings. That is how you turn one detection into a family-wide block list.
Retrohunt runs a YARA rule across VT's historic corpus (a rolling window of recent submissions). Write a rule that captures your threat's unique bytes, launch the job, and collect every matching sample VT has ever seen.
Livehunt keeps a YARA ruleset running against every new submission. When something matches — a new variant of your tracked family, a document abusing your brand — VT notifies you. It is the closest thing to a standing tripwire across the global malware stream.
A sloppy YARA rule (a single common string, no header anchor) will match thousands of files and bury real hits in noise — and burn your notification quota. Anchor on the file type, require multiple distinctive strings, and test with Retrohunt before promoting a rule to Livehunt.
09 — AUTOMATION & SIEM INTEGRATION
ADVANCEDThe real-world task: take a CSV of hashes from quarantine or a hunt, enrich each against VT, and write a verdict column — without blowing the 4/min / 500-day free quota. The pattern is: cache locally, skip anything already known, sleep between calls, and handle the 404 for hashes VT has never seen.
Persist the cache to a small SQLite table or Redis in a real SOC. Most hashes you see are repeats; a warm cache turns a 500-call day into a handful of genuinely new lookups.
Before you call VT, you need the list of indicators worth spending quota on. These queries pull distinct, recent file hashes from your telemetry so your enrichment job only touches what is new. Run the query, feed the output into enrich_hashes.py, and write the verdict back as a watchlist or lookup.
In Sentinel you do not have to script the write-back: the built-in VirusTotal Logic App connector enriches entities inside an automation playbook. In Splunk, the VirusTotal app on Splunkbase adds a | virustotal search command. The scripted path above is the portable, no-license-required option.
Wazuh ships a native VirusTotal integration that checks file hashes from its File Integrity Monitoring against VT and raises an alert on a hit. For everything else, a cron-driven enrichment job keeps your indicator store warm.
- caches every verdict and never re-queries a known hash
- sleeps to stay under the per-minute and daily quota
- treats NotFoundError as "unknown", not "clean"
- writes verdicts back where analysts already look (SIEM watchlist / lookup)
- uses a licensed Premium key if the SOC is a commercial operation
10 — QUOTAS, OPSEC & TROUBLESHOOTING
REFERENCEv3 returns a JSON error body with a machine-readable code alongside the HTTP status. Handle these explicitly — especially the difference between 429 (slow down) and a 404 (VT has never seen this indicator).
| Status | code | Meaning & fix |
|---|---|---|
| 401 | AuthenticationRequiredError | Missing/invalid x-apikey header. Check the env var is exported. |
| 403 | ForbiddenError | Your tier lacks this endpoint (e.g. Intelligence on a free key). |
| 404 | NotFoundError | Object not in corpus. This is "unknown", not "clean". Consider uploading. |
| 429 | QuotaExceededError | Rate or daily quota hit. Back off; no queue exists — the request is dropped. |
| 400 | BadRequestError | Malformed ID (e.g. bad URL base64) or query syntax. |
| Symptom | Likely cause | Fix |
|---|---|---|
| vt: command not found | Binary not on PATH | Move vt to /usr/local/bin or add GOBIN to PATH |
| 401 on every call | Key not exported / typo | echo $VT_API_KEY; re-run vt init |
| Script stalls then 429 | No inter-call delay | Sleep ≥15 s; cache; batch under 500/day |
| URL lookup 400 | Wrong URL ID encoding | base64url, strip = padding, or submit to get ID |
| ModuleNotFoundError: vt | venv not activated | source .venv/bin/activate before running |
| Verdict looks stale | Cached last_analysis | Re-submit to force a fresh multi-engine scan |
The VT API is a force multiplier, but the two mistakes that actually hurt organisations are uploading things they should not and treating a clean result as proof of safety. Internalise these before you point automation at it.
Hash first, upload second. A hash lookup is passive and leaks nothing. An upload publishes the file. Searching VT for a known-bad hash also signals to a premium-subscriber adversary that someone is investigating it — operationally relevant during an active intrusion.
Only enrich indicators you are authorised to investigate, and use a licensed Premium key for any commercial or production SOC workflow. The free key is research-only; building it into a paid service breaches VT's terms.
11 — SOURCES & REFERENCES
REFERENCEPrimary documentation and official tooling used throughout this guide. Always defer to the current VT documentation for endpoint and quota changes.
◈ Put VirusTotal to Work
This guide is defensive tooling for authorised use — enrich only indicators you are permitted to investigate, and never upload sensitive files. Pair VT enrichment with CyberHawk's IOC Scanner to triage indicators fast, and browse more hands-on SOC tooling on the CyberHawk blog.
Want the detection and response side? Our SOP library covers IOC-match and malware triage playbooks that consume exactly this kind of enrichment.
◈ Stay Connected
Follow CyberHawk Threat Intel for threat intelligence, deployment guides and hands-on SOC tooling content.
"They can't exploit you if you are the Exploit."