# VirusTotal API v3 Reference Guide for using the VirusTotal API v3 for file lookups during malware triage. ## Authentication All API requests require an API key passed via the `x-apikey` HTTP header. **Getting an API key:** 1. Create a free account at https://www.virustotal.com/ 2. Navigate to your profile > API Key 3. Copy the API key **Setting the environment variable:** ```bash # Linux/macOS export VT_API_KEY="your-api-key-here" # Windows PowerShell $env:VT_API_KEY = "your-api-key-here" # Windows CMD set VT_API_KEY=your-api-key-here ``` ## Rate Limits | Account Type | Requests/Minute | Requests/Day | Lookups/Month | |-------------|-----------------|--------------|---------------| | Free | 4 | 500 | 15,500 | | Premium | Varies | Varies | Varies | **Rate limit handling:** - HTTP 429 response indicates rate limit exceeded - Implement exponential backoff: wait 15s, 30s, 60s between retries - Cache results locally to avoid redundant lookups ## Core Endpoints ### Look Up a File by Hash ``` GET https://www.virustotal.com/api/v3/files/{id} ``` Where `{id}` is the SHA256, SHA1, or MD5 hash of the file. **Example with curl:** ```bash SHA256="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" curl -s \ -H "x-apikey: $VT_API_KEY" \ "https://www.virustotal.com/api/v3/files/$SHA256" ``` **Example with Python:** ```python import requests import os api_key = os.environ["VT_API_KEY"] sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" response = requests.get( f"https://www.virustotal.com/api/v3/files/{sha256}", headers={"x-apikey": api_key}, timeout=30 ) if response.status_code == 200: data = response.json() attrs = data["data"]["attributes"] stats = attrs["last_analysis_stats"] print(f"Detections: {stats['malicious']}/{sum(stats.values())}") elif response.status_code == 404: print("File not found in VirusTotal") elif response.status_code == 429: print("Rate limit exceeded") ``` ### Upload a File for Scanning **Small files (< 32 MB):** ``` POST https://www.virustotal.com/api/v3/files ``` ```bash curl -s \ -H "x-apikey: $VT_API_KEY" \ -F "file=@suspicious_file" \ "https://www.virustotal.com/api/v3/files" ``` **Large files (32 MB - 650 MB):** First, get an upload URL: ``` GET https://www.virustotal.com/api/v3/files/upload_url ``` Then POST to the returned URL. ### Re-scan a Previously Submitted File ``` POST https://www.virustotal.com/api/v3/files/{id}/analyse ``` ### Get File Behavior Report ``` GET https://www.virustotal.com/api/v3/files/{id}/behaviour_summary ``` ## Response Parsing ### File Report Structure ```json { "data": { "type": "file", "id": "", "attributes": { "last_analysis_stats": { "malicious": 45, "suspicious": 2, "undetected": 20, "harmless": 0, "timeout": 3, "confirmed-timeout": 0, "failure": 2, "type-unsupported": 0 }, "last_analysis_results": { "Kaspersky": { "category": "malicious", "engine_name": "Kaspersky", "engine_version": "...", "result": "Trojan.Win32.Agent.xxx", "method": "exact", "engine_update": "20250115" } }, "popular_threat_classification": { "suggested_threat_label": "trojan.agent/generic", "popular_threat_category": [ {"count": 30, "value": "trojan"} ], "popular_threat_name": [ {"count": 15, "value": "agent"} ] }, "type_description": "Win32 EXE", "size": 245760, "sha256": "...", "sha1": "...", "md5": "...", "ssdeep": "...", "magic": "PE32 executable (GUI) Intel 80386, for MS Windows", "tags": ["peexe", "assembly", "overlay"], "names": ["sample.exe", "malware.exe"], "first_submission_date": 1705123456, "last_submission_date": 1705234567, "last_analysis_date": 1705345678, "creation_date": 1705012345, "pe_info": { "compiler_product_versions": ["..."], "entry_point": 12345, "imphash": "abc123...", "machine_type": 332, "sections": [], "imports": {} }, "sigma_analysis_stats": {}, "sandbox_verdicts": {} } } } ``` ### Key Fields to Extract | Field | Path | Description | |-------|------|-------------| | Detection count | `data.attributes.last_analysis_stats.malicious` | Number of AV engines detecting as malicious | | Threat label | `data.attributes.popular_threat_classification.suggested_threat_label` | Consensus threat name | | File type | `data.attributes.type_description` | VirusTotal's file type classification | | Import hash | `data.attributes.pe_info.imphash` | Import hash for PE files (useful for clustering) | | First seen | `data.attributes.first_submission_date` | Unix timestamp of first submission | | Tags | `data.attributes.tags` | File tags (peexe, upx, signed, etc.) | | Sandbox results | `data.attributes.sandbox_verdicts` | Sandbox analysis verdicts | ### Interpreting Results **Detection ratio guidelines:** - `0/70` - Clean or undetected (could be new/targeted malware) - `1-5/70` - Possibly false positive or very new threat - `5-20/70` - Likely malicious, possibly new or polymorphic - `20-50/70` - Confirmed malicious - `50+/70` - Well-known malware **Important caveats:** - Zero detections does NOT mean the file is safe - Some AV engines specialize in different malware types - Detection names vary wildly between vendors - First submission date helps estimate when the threat appeared ## Searching for Related Samples ### Search by Attributes ``` GET https://www.virustotal.com/api/v3/intelligence/search?query={query} ``` Useful search queries: ``` # Files with same import hash imphash:"abc123..." # Files from same signer signature:"Company Name" # Files contacting a specific domain itw:"evil-domain.com" # Files by size range and type type:peexe size:100KB-500KB positives:10+ # Files by YARA rule match content:{48 8B 05 ?? ?? ?? ?? 48 89 44 24} ``` ## Error Handling Best Practices ```python import time import requests def vt_lookup_with_retry(sha256, api_key, max_retries=3): """Query VT with retry logic for rate limiting.""" url = f"https://www.virustotal.com/api/v3/files/{sha256}" headers = {"x-apikey": api_key} for attempt in range(max_retries): try: resp = requests.get(url, headers=headers, timeout=30) if resp.status_code == 200: return resp.json() elif resp.status_code == 404: return None # File not found elif resp.status_code == 429: wait_time = 15 * (2 ** attempt) # Exponential backoff time.sleep(wait_time) continue else: resp.raise_for_status() except requests.Timeout: if attempt < max_retries - 1: continue raise except requests.ConnectionError: if attempt < max_retries - 1: time.sleep(5) continue raise raise Exception("Max retries exceeded for VirusTotal API") ``` ## Security Considerations - **Never hardcode API keys** in scripts - always use environment variables - **Be aware of data sharing** - uploading files to VT makes them available to premium users - **Check your organization's policy** before uploading samples to any public service - **Use hash lookups first** before uploading to check if the file is already known - **Consider privacy** - files may contain sensitive data (credentials, PII, proprietary code)