Total check. We use the VirusTotal API in our projects.

Hacker

Professional
Messages
1,044
Reaction score
804
Points
113
The content of the article
  • Get the API Key
  • API versions
  • API VirusTotal. Version 2
  • API VirusTotal. Version 3
  • Conclusion

To use the VirusTotal APIs without restrictions, you need to get a key, which costs a significant amount - prices start at 700 euros per month. Moreover, a private person will not be given the key, even if he is willing to pay.
However, you should not despair, since the service provides the basic functions for free and limits us only by the number of requests - no more than two per minute. Well, you have to put up with it.

Get the API Key
So, first of all we need to register on the site. There are no problems here - I'm sure you can handle it. After registration, we take the access key by going to the API key menu item.

6688c3e6458882c56fa36.png


API versions
The current version of the API is currently number 2. But at the same time, there is already a new option - number 3. This version of the API is still in beta, but it is already quite possible to use it, especially since the possibilities it provides are much wider.

So far, the developers recommend using the third version only for experiments or for non-critical projects. We will analyze both versions. The access key is the same for them.

API VirusTotal. Version 2
As with other popular web services, the API is about sending requests over HTTP and receiving responses.

The API of the second version allows:
  • send files for verification;
  • receive a report on previously checked files using the file identifier (hash of the SHA-256, SHA-1 or MD5 file, or the value scan_idfrom the response received after the file was sent);
  • send URL for crawling to the server;
  • receive a report on previously verified URLs using either the URL itself or the value scan_idfrom the response received after the URL was sent to the server;
  • receive a report by IP address;
  • receive a report on the domain name.

Errors
If the request was processed correctly and no errors occurred, a 200 (OK) code will be returned.

If an error occurs, then there may be such options:
  • 204 - error of type Request rate limit exceeded. Occurs when the quota of the allowed number of requests has been exceeded (for a free key, the quota is four requests per minute);
  • 400 - Bad request error. Occurs when an incorrectly formed request, for example, if there are no required arguments or they have invalid values;
  • 403 - Forbidden error. It occurs when trying to use API functions available only with a paid key, when it is not available.

If the request is formed correctly (HTTP status code is 200), the response will be a JSON object, the body of which contains at least two fields:
  • response_code - if the requested object (file, URL, IP-address or domain name) is in the VirusTotal database (that is, it was checked before) and information about this object can be obtained, then the value of this field will be equal to one; if the requested object is in the queue for analysis, the field value will be -2; if the requested object is absent in the VirusTotal database - equal to zero;
  • verbose_msg provides a more detailed description of the value response_code(e.g. Scan finished, information embedded after submitting the file for scanning).
The rest of the information contained in the response JSON object depends on which API function was used.

Sending a File to a Server for Scanning
To send a file for scanning, you need to form a POST request to the address https://www.virustotal.com/vtapi/v2, while in the request you need to specify the API access key and transfer the file itself (there is a limit on the file size - no more than 32 MB). It might look like this (using Python):
Code:
import json
import requests
...
api_url = 'https://www.virustotal.com/vtapi/v2/file/scan'
params = dict (apikey = '<access key>')
with open ('<path to file>', 'rb') as file:
  files = dict (file = ('<path to file>', file))
  response = requests.post(api_url, files=files, params=params)
if response.status_code == 200:
  result=response.json()
  print(json.dumps(result, sort_keys=False, indent=4))
...

Here, instead of a line, <ключ доступа>you need to insert your API access key, and instead <путь к файлу>- the path to the file that you will send to VirusTotal. If you do not have the requests library, then install it with the command pip install requests.

In response, if everything went well and the HTTP status code is 200, we will get something like this:
Code:
{
  "response_code": 1,
  "verbose_msg": "Scan request successfully queued, come back later for the report",
  "scan_id": "275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f-1577043276",
  "resource": "275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f",
  "sha1": "3395856ce81f2b7382dee72602f798b642f14140",
  "md5": "44d88612fea8a8f36de82e1278abb02f",
  "sha256": "275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f",
  "permalink": "https://www.virustotal.com/file/275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f/analysis/1577043276/" 
}

Here we see the values response_codeand verbose_msg, as well as the hashes of the SHA-256, SHA-1 and MD5 file, a link to the results of scanning the file on the website, permalinkand the file ID scan_id.

Get a report of the last scanned file
Using any of the hashes or the value scan_idfrom the response, you can get a report on the last scan of the file (if the file has already been uploaded to VirusTotal). To do this, you need to form a GET request and specify the access key and file identifier in the request. For example, if we have scan_idfrom the previous example, then the request will look like this:
Code:
import json
import requests
...
api_url = 'https://www.virustotal.com/vtapi/v2/file/report'
params = dict(apikey='<ключ доступа>', resource='275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f-1577043276')
response = requests.get(api_url, params=params)
if response.status_code == 200:
  result=response.json()
  print(json.dumps(result, sort_keys=False, indent=4))
...

If successful, we will see the following in response:
Code:
{
  "response_code": 1,
  "verbose_msg": "Scan finished, information embedded",
  "resource": "275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f",
  "sha1": "3395856ce81f2b7382dee72602f798b642f14140",
  "md5": "44d88612fea8a8f36de82e1278abb02f",
  "sha256": "275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f",
  "scan_date": "2019-11-27 08:06:03",
  "permalink": "https://www.virustotal.com/file/275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f/analysis/1577043276/",
  "positives": 59,
  "total": 69,
  "scans": {
    "Bkav": {
      "detected": true,
      "version": "1.3.0.9899",
      "result": "DOS.EiracA.Trojan",
      "update": "20191220"
    },
    "DrWeb": {
      "detected": true,
      "version": "7.0.42.9300",
      "result": "EICAR Test File (NOT a Virus!)",
       "update": "20191222"
    },
    "MicroWorld-eScan": {
      "detected": true,
      "version": "14.0.297.0",
      "result": "EICAR-Test-File",
      "update": "20191222"
    },
    ...
  ...
  "Panda": {
    "detected": true,
    "version": "4.6.4.2",
    "result": "EICAR-AV-TEST-FILE",
    "update": "20191222"
  },
  "Qihoo-360": {
    "detected": true,
    "version": "1.0.0.1120",
    "result": "qex.eicar.gen.gen",
    "update": "20191222"
  }
}

Here, as in the first example, we obtain a hash value of a file scan_id, permalinkthe values response_codeand verbose_msg. We also see the results of scanning the file with antiviruses and the general results of the assessment total- how many antivirus engines were involved in the positivesscan and - how many antiviruses gave a positive verdict.

To display the results of scanning by all antiviruses in a digestible form, you can, for example, write something like this:
Code:
import requests
...
api_url = 'https://www.virustotal.com/vtapi/v2/file/report'
params = dict(apikey='<access key>', resource='275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f-1577043276')
response = requests.get(api_url, params=params)
if response.status_code == 200:
  result=response.json()
  for key in result['scans']:
    print(key)
    print('  Detected: ', result['scans'][key]['detected'])
    print('  Version: ', result['scans'][key]['version'])
    print('  Update: ', result['scans'][key]['update'])
    print('  Result: ', 'result['scans'][key]['result'])
...

a33e631092cc101288ef6.png


Sending URL to server for crawling
To send a URL for crawling, we need to form and send a POST request containing the access key and the URL itself:
Code:
import json
import requests
...
api_url = 'https://www.virustotal.com/vtapi/v2/url/scan'
params = dict (apikey = '<access key>', url = 'https: //hacker.com/author/drobotun/')
response = requests.post(api_url, data=params)
if response.status_code == 200:
  result=response.json()
  print(json.dumps(result, sort_keys=False, indent=4))
...

In response, we will receive approximately the same as when sending the file, with the exception of the hash values. The contents of the field scan_idcan be used to get a crawl report for this URL.

Get a report on the results of a URL crawl
Let's form a GET request with an access key and specify either the URL itself as a string, or the value scan_idobtained using the previous function. It will look like this:
Code:
import json
import requests
...
api_url = 'https://www.virustotal.com/vtapi/v2/url/report'
params = dict(apikey='<access key>', resource='https://hacker.com/author/drobotun/', scan=0)
response = requests.get(api_url, params=params)
if response.status_code == 200:
  result=response.json()
  print(json.dumps(result, sort_keys=False, indent=4))
...

In addition to the access key and the URL string, there is an optional parameter here scan- by default it is equal to zero. If its value is equal to one, then when there is no information about the requested URL in the VirusTotal database (the URL has not been checked before), this URL will be automatically sent to the server for verification, after which we will receive the same information in response as when sending the URL. to the server. If this parameter is equal to zero (or was not set), we will receive a report about this URL or (if there is no information about it in the VirusTotal database) a response like this:
Code:
{
  "response_code": 0,
  "resource": "<requested URL>",
  "verbose_msg": "Resource does not exist in the dataset"
}

Obtaining information about IP addresses and domains
To check IP addresses and domains, you need to form and send a GET request with a key, the name of the checked domain, or an IP as a string. For domain verification, it looks like this:
Code:
...
api_url = 'https://www.virustotal.com/vtapi/v2/domain/report'
params = dict (apikey = '<access key>', domain = <'domain name'>)
response = requests.get(api_url, params=params)
...

To check the IP address:
Code:
...
api_url = 'https://www.virustotal.com/vtapi/v2/ip-address/report'
params = dict (apikey = '<access key>', ip = <'IP address'>)
response = requests.get(api_url, params=params)
...

The answers to such queries are voluminous and contain a lot of information. For example, for IP 178.248.232.27(this is the IP of the "Hacker"), the beginning of the report received from the VirusTotal server looks like this:
Code:
{
  "country": "EN",
  "response_code": 1,
  "as_owner": "HLL LLC",
  "verbose_msg": "IP address in dataset",
  "continent": "EU",
  "detected_urls": [
    {
    "url": "https://hacker.com/author/drobotun/",
    "positives": 1,
    "total": 72,
    "scan_date": "2020-12-18 19:45:02"
    },
    {
    "url": "https://hacker.com/2020/12/18/linux-backup/",
    "positives": 1,
    "total": 72,
    "scan_date": "2020-12-18 16:35:25"
    },
    ...
  ]
}

API VirusTotal. Version 3
The third version of the API has a lot more features compared to the second - even with a free key. Moreover, while experimenting with the third version, I did not notice that the number of uploaded objects (files or addresses) to the server was limited within a minute. It looks like the beta restrictions are not in effect yet.

The functions of the third version of the API are designed using REST principles and are easy to understand. The access key is passed here in the request header.

Errors
In the third version of the API, the list of errors (and, accordingly, HTTP status codes) has expanded. Were added:
  • 401 - an error of the User Not Active Error type, it occurs when the user account is inactive;
  • 401 - an error of type Wrong Credentials Error, occurs if an incorrect access key is used in the request;
  • 404 Not Found Error occurs when the requested parse object is not found;
  • 409 - an error of type Already Exists Error, occurs when the resource already exists;
  • 429 - Quota Exceeded Error occurs when one of the quotas for the number of requests (minute, daily, or monthly) is exceeded. As I said, during my experiments, there were no restrictions on the number of requests per minute, although I used a free key;
  • 429 - an error like Too Many Requests Error, occurs when a large number of requests in a short time (may be caused by server load);
  • 503 - Transient Error, a temporary server error, in which a retry of the request can be triggered.
In case of an error, in addition to the status code, the server returns additional information in the form of JSON. True, as it turned out, not for all HTTP status codes: for example, for a 404 error, the additional information is a regular string.

The JSON format for the error is as follows:
Code:
{
  "error": {
    "code": "<HTTP status code> ",
    "message": "<message describing the error>"
  }
}

File handling functions
The third version of the API allows you to:
  • upload files for analysis to the server;
  • get a URL for uploading a file larger than 32 MB to the server;
  • receive reports on the results of file analysis;
  • re-analyze the file;
  • get comments from VirusTotal users for the file you need;
  • send your comment to a specific file;
  • view the voting results for a specific file;
  • vote for the file;
  • get extended information about a file.
To upload a file to the server, you need to send it via a POST request. You can do it like this:
Code:
...
api_url = 'https://www.virustotal.com/api/v3/files'
headers = {'x-apikey': '<API access key>'}
with open ('<path to file>', 'rb') as file:
  files = {'file': ('<path to file>', file)}
  response = requests.post(api_url, headers=headers, files=files)
...

In response, we will receive the following:
Code:
{
  "data": {
    "id": "ZTRiNjgxZmJmZmRkZTNlM2YyODlkMzk5MTZhZjYwNDI6MTU3NzIxOTQ1Mg==",
    "type": "analysis"
  }
}

Here we see a value idthat serves as a file identifier. This identifier must be used to obtain information about the parsing of a file in GET requests of the type /analyses(we will talk about this a little later).

To get the URL for downloading a large file (over 32 MB), you need to send a GET request, in which the URL is specified https://www.virustotal.com/api/v3/files/upload_url. Insert the access key into the header:
Code:
...
api_url = 'https://www.virustotal.com/api/v3/files/upload_url'
headers = {'x-apikey': '<API access key>'}
response = requests.get(api_url, headers=headers)
...

In response, we will receive a JSON with the address where the file should be downloaded for analysis. The resulting URL can only be used once.

To get information about a file that the service has already parsed, you need to make a GET request with the file identifier in the URL (it can be a SHA-256, SHA-1 or MD5 hash). As in the previous cases, we indicate the access key in the header:
Code:
...
api_url = 'https://www.virustotal.com/api/v3/files/<file id value>'
headers = {'x-apikey': '<API access key>'}
response = requests.get(api_url, headers=headers)
...

In response, we will receive a file scan report, where, in addition to the scan results with all VirusTotal antiviruses, there will be a lot of additional information, the composition of which depends on the type of scanned file. For example, for executable files, you can see information about the following attributes:
Code:
{
  "attributes": {
    "authentihash": "8fcc2f670a166ea78ca239375ed312055c74efdc1f47e79d69966461dd1b2fb6",
    "creation_date": 1270596357,
    "exiftool": {
      "CharacterSet": "Unicode",
      "CodeSize": 20480,
      "CompanyName": "TYV",
      "EntryPoint": "0x109c",
      "FileFlagsMask": "0x0000",
      "FileOS": "Win32",
      "FileSubtype": 0,
      "FileType": "Win32 EXE",
      "FileTypeExtension": "exe",
      "FileVersion": 1.0,
      "FileVersionNumber": "1.0.0.0",
      "ImageFileCharacteristics": "No relocs, Executable, No line numbers, No symbols, 32-bit",
      ...
      ...
      "SubsystemVersion": 4.0,
      "TimeStamp": "2010:04:07 00:25:57+01:00",
      "UninitializedDataSize": 0
    },
    ...
  }
}

Or, for example, information about sections of an executable file:
Code:
{
  "sections": [
    {
      "entropy": 3.94,
      "md5": "681b80f1ee0eb1531df11c6ae115d711",
      "name": ".text",
      "raw_size": 20480,
      "virtual_address": 4096,
      "virtual_size": 16588
    },
    {
      "entropy": 0.0,
      "md5": "d41d8cd98f00b204e9800998ecf8427e",
      "name": ".data",
      "raw_size": 0,
      "virtual_address": 24576,
      "virtual_size": 2640
    },
    ...
  }
}

If the file has not been previously uploaded to the server and has not yet been analyzed, then in response we will receive a Not Found Error with an HTTP status code equal to 404:
Code:
{
  "error": {
    "code": "NotFoundError",
    "message": "File \"<идентификатор файла>" not found"
  }
}

To re-parse the file, you also need to send a GET request to the server, in which we put the file identifier in the URL, and at the end we add /analyse:
Code:
...
api_url = 'https://www.virustotal.com/api/v3/files/<file id value> / analyze'
headers = {'x-apikey': '<API access key>'}
response = requests.get(api_url, headers=headers)
...

The response will include the same file descriptor as in the first case - when uploading the file to the server. And just like in the first case, the identifier from the descriptor can be used to get information about the file parsing through a GET request of the type /analyses.

You can view the comments of users of the service, as well as the results of voting on the file, by sending a corresponding GET request to the server. For comments:
Code:
...
api_url = 'https://www.virustotal.com/api/v3/files/<file id value> / comments'
headers = {'x-apikey': '<API access key>'}
response = requests.get(api_url, headers=headers)
...

To get the voting results:
Code:
...
api_url = 'https://www.virustotal.com/api/v3/files/<file id value> / votes'
headers = {'x-apikey': '<API access key>'}
response = requests.get(api_url, headers=headers)
...

In both cases, you can use an additional parameter limitthat determines the maximum number of comments or votes in a response to a request. You can use this parameter, for example, like this:
Code:
...
limit = {'limit': str (<number of votes in the answer>)}
api_url = 'https://www.virustotal.com/api/v3/files/<file id value> / votes'
headers = {'x-apikey': '<API access key>'}
response = requests.get(api_url, headers=headers, params=limit)
...

To post your comment or vote for a file, create a POST request, and send the comment or vote as a JSON object:
Code:
...
## To send voting results
votes = {'data': {'type': 'vote', 'attributes': {'verdict': <'malicious' или 'harmless'>}}}
api_url = 'https://www.virustotal.com/api/v3/files/<file id value> / votes'
headers = {'x-apikey': '<API access key>'}
response = requests.post(api_url, headers=headers, json=votes)
...
## To post a comment
comments = {'data': {'type': 'vote', 'attributes': {'text': <comment text>}}}
headers = {'x-apikey': '<API access key>'}
api_url = 'https://www.virustotal.com/api/v3/files/<file id value> / comments'
response = requests.post(api_url, headers=headers, json=comments)
...

To get more information about a file, you can request details about the objects associated with it. In this case, the objects can be characterized, for example, the behavior of the file (object behaviours) or URL, IP-addresses, domain names (items contacted_urls, contacted_ips, contacted_domains).

The most interesting of all is the object behaviours. For example, for executable files, it will include information about loadable modules, processes created and launched, operations with the file system and registry, network operations.

To get this information, we send a GET request:
Code:
api_url = 'https://www.virustotal.com/api/v3/files/< file id value> / behaviors'
headers = {'x-apikey': '<API access key>'}
response = requests.get(api_url, headers=headers)

The response will contain a JSON object with information about the behavior of the file:
Code:
{
  "data": [
    {
      "attributes": {
        "analysis_date": 1548112224,
        "command_executions": [
          "C:\\WINDOWS\\system32\\ntvdm.exe -f -i1",
          "/bin/bash /private/tmp/eicar.com.sh"
        ],
        "has_html_report": false,
        "has_pcap": false,
        "last_modification_date": 1577880343,
        "modules_loaded": [
          "c:\\windows\\system32\\user32.dll",
          "c:\\windows\\system32\\imm32.dll",
          "c:\\windows\\system32\\ntdll.dll"
        ]
      },
      ...
    }
  ]
}

Functions for working with URLs
The list of possible URL operations includes:
  • sending the URL to the server for analysis;
  • getting information about the URL;
  • parsing the URL;
  • receiving comments from VirusTotal users at the desired URL;
  • sending your comments to a specific URL;
  • getting voting results for a specific URL;
  • sending your vote for any URL;
  • getting extended information about the URL;
  • obtaining information about the domain or IP-address of the desired URL.

Most of these operations (except for the last one) are performed in the same way as those with files. In this case, the URL identifier can be either a string with a URL encoded in Base64 without the additional "equals" signs, or a SHA-256 hash of the URL. This can be done like this:
Code:
## For Base64
import base64
...
id_url = base64.urlsafe_b64encode(url.encode('utf-8')).decode('utf-8').rstrip('=')
...
## For SHA-256
import hashlib
...
id_url = hashlib.sha256(url.encode()).hexdigest()

To send a URL for analysis, you need to use a POST request:
Code:
data = {'url': '<url name string>'}
api_url = 'https://www.virustotal.com/api/v3/urls'
headers = {'x-apikey': '<API access key>'}
response = requests.post(api_url, headers=headers, data=data)

In response, we will see the URL descriptor (similar to the file descriptor):
Code:
{
  "data": {
    "id": "u-1a565d28f8412c3e4b65ec8267ff8e77eb00a2c76367e653be774169ca9d09a6-1577904977",
    "type": "analysis"
  }
}

We use the identifier idfrom this descriptor to get information about the parsing of a file through a GET request of the type /analyses(about this request closer to the end of the article).

You can get information about domains or IP addresses associated with a URL by applying a GET request of the type /network_location(here we use Base64 or SHA-256 URL identifier):
Code:
api_url = 'https://www.virustotal.com/api/v3/urls/< url id (Base64 or SHA-256)> / network_location'
headers = {'x-apikey': '<API access key>'}
response = requests.post(api_url, headers=headers)

The rest of the URL operations are performed in the same way as similar file operations.

Functions for working with domains and IP-addresses
This feature list includes:
  • obtaining information about a domain or IP-address;
  • receiving comments from VirusTotal users for the desired domain or IP-address;
  • sending your comments to a specific domain or IP address;
  • receiving voting results for a specific domain or IP address;
  • sending a vote for a domain or IP address;
  • obtaining extended information about a domain or IP-address.
All these operations are implemented similarly to the same operations with files or with a URL. The difference is that domain names or IP address values are used directly, not their identifiers.

For example, you can get information about a domain www.hacker.com like this:
Code:
api_url = 'https://www.virustotal.com/api/v3/domains/www.hacker.com'
headers = {'x-apikey': '<API access key>'}
response = requests.get(api_url, headers=headers)

And, for example, look at the comments on the IP address 178.248.232.27 - like this:
Code:
api_url = 'https://www.virustotal.com/api/v3/ip_addresses/178.248.232.27/comments'
headers = {'x-apikey': '<API access key>'}
response = requests.get(api_url, headers=headers)

GET request like / analyzes
Such a request allows you to get information about the results of the analysis of files or URLs after they are uploaded to the server or after re-analysis. In this case, you must use the identifier contained in the idfile descriptor field, or the URL obtained as a result of sending requests to download a file or URL to the server, or as a result of re-parsing the file or URL.

For example, you can create a similar request for a file like this:
Code:
TEST_FILE_ID = 'ZTRiNjgxZmJmZmRkZTNlM2YyODlkMzk5MTZhZjYwNDI6MTU3NjYwMTE1Ng=='
...
api_url = 'https://www.virustotal.com/api/v3//analyses/' + TEST_FILE_ID
headers = {'x-apikey': '<API access key>'}
response = requests.get(api_url, headers=headers)

And the option for the URL:
Code:
TEST_URL_ID = 'u-dce9e8fbe86b145e18f9dcd4aba6bba9959fdff55447a8f9914eb9c4fc1931f9-1576610003'
...
api_url = 'https://www.virustotal.com/api/v3//analyses/' + TEST_URL_ID
headers = {'x-apikey': '<API access key>'}
response = requests.get(api_url, headers=headers)

Conclusion
We have gone through all the main functions of the VirusTotal API. You can borrow the given code for your projects. If you are using the second version, you will need to be careful not to send requests too often, but in the third version there is no such restriction yet. I recommend choosing it, since the possibilities here are also much wider. Besides, sooner or later it will become the main one.
 
Top