VT-PY(1)
π¦ VT-PY(1) β the vt-py manual, bestie edition π¦
πΈ NAME πΈ
okay so, <pink>vt-py</pink> β that's literally the name β this is the vt-py Documentation, and the headline is: vt-py is the official Python client library for the VirusTotal API v3. π
Real talk, this library requires Python 3.7.0+, and Python 2.x is not supported. Why? Because vt-py makes use of the new async/await syntax for implementing asynchronous coroutines, and Python 2.x just doesn't have the range, bestie. π
Not supporting Python 2.x was a difficult decision to make, lowkey β we know Python 2.x is still popular among VirusTotal users, but in the long run we think it's the right one. β¨
Here's the tea: Python 2.7 has its days numbered, and the new concurrency features included in Python 3.5+ are literally perfect for creating highly performant code without having to deal with multi-threading or multi-processing. no cap. π₯
π HOW TO INSTALL π
The easiest and recommended way of installing vt-py is using pip, periodt:
$ pip install vt-py
Alternatively, you can get the source code directly from the GitHub and run setup.py. For getting the code you can either clone the public repository:
$ git clone https://github.com/VirusTotal/vt-py.git
$ cd vt-py
Or, download the tarball for the latest release and uncompress it:
$ tar -zxvf vt-py-X.Y.Z.tar.gz
$ cd vt-py-X.Y.Z
Once you have the code you can install it with:
$ pip install .
slay. you're installed. π
π¦ OVERVIEW π¦
The API for this library is relatively small and shares the same concepts and principles seen in the underlying REST API. For this reason we highly recommend you familiarize yourself with these concepts before continuing. iykyk. π
While using this library you may have the impression that it's very similar to other general-purpose HTTP libraries like requests, as you will see very generic APIs like vt.Client.get(), vt.Client.post() and so on. In fact, you will find yourself relying on the REST API documentation in order to find the right endpoint where to send a request to, or learn about the attributes exported by some object. This has been a deliberate decision. π
We wanted vt-py to be as lightweight and generic as possible, so that changes in the REST API don't always require a new version of this client library β but at the same time offering the right abstractions so that you don't need to deal with details like setting HTTP headers, serializing and deserializing JSON, etc. it's giving "the best of both worlds", honestly.
So like, this is not a high-level library that completely abstracts you out of the underlying REST API. quite the contrary, actually. this library is more like a HTTP library that has been enriched with features specifically tailored to work with the VirusTotal API. π
π QUICKSTART π
π Get information about a file π
Start by importing the vt module:
>>> import vt
Create a client β replace <apikey> with your actual VirusTotal API key:
>>> client = vt.Client("<apikey>")
Ask for the file you are interested in β you can replace the hash in the example with some other SHA-256, SHA-1 or MD5:
>>> file = client.get_object("/files/44d88612fea8a8f36de82e1278abb02f")
Now file is an instance of vt.Object that contains information about the requested file. This object has the attributes returned in the API response, which are listed in the VirusTotal API v3 documentation. Some examples:
>>> file.size
68
>>> file.sha256
'275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f'
>>> file.type_tag
'text'
>>> file.last_analysis_stats
{'failure': 0, 'harmless': 0, 'malicious': 62, 'suspicious': 0, 'timeout': 0, 'type-unsupported': 9, 'undetected': 2}
Alternatively, you can use the vt.Object.get() method for retrieving object's attributes:
>>> file.get("size")
68
>>> file.get("type_tag")
'text'
This method is useful for those attributes that are optional and do not appear in all files. For example, a Portable Executable (PE) file won't have the pdf_info attribute, and a PDF document won't have the pe_info attribute. This means that file.pe_info will fail for PDF files β but with file.get("pe_info") you are on the safe side, as it will return None if the pe_info attribute is not present. π«Ά no more surprise tracebacks, bestie.
π¦ Get information about an URL π¦
Create a client as explained in the previous section and ask for the desired URL as follows:
>>> url_id = vt.url_id("http://www.virustotal.com")
>>> url = client.get_object("/urls/{}", url_id)
In this case the code is not as straightforward as it was for getting a file. While retrieving a file any of its hashes can be used as the file identifier, but with URLs it's a bit more complicated β you must use vt.url_id() for generating the appropriate identifier. You can find more information about why this is necessary in: https://docs.virustotal.com/reference/url. π it's just how it is.
Also notice how we are using a placeholder {} in the path. The placeholder will be replaced with the value of url_id. This works exactly like Python's new-style string formatting using the .format() function. This other code is equivalent:
>>> url = client.get_object("/urls/{}".format(url_id))
The returned object contains the URL attributes. Some examples:
>>> url.times_submitted
213730
>>> url.last_analysis_stats
{'harmless': 61, 'malicious': 0, 'suspicious': 1, 'timeout': 0, 'undetected': 8}
π₯ Scan a file π₯
Before scanning a file it's highly recommended that you look it up as described in Get information about a file. If the file already exists and the latest analysis is fresh enough, you can use that instead of scanning the file again. If not, you can scan it with:
>>> with open("/path/to/file", "rb") as f:
>>> analysis = client.scan_file(f)
When vt.Client.scan_file() returns, the analysis is not completed yet β the object returned only has the analysis ID but not attributes. In order to track the status of the analysis you must ask for the analysis object until its status is completed:
>>> while True:
>>> analysis = client.get_object("/analyses/{}", analysis.id)
>>> print(analysis.status)
>>> if analysis.status == "completed":
>>> break
>>> time.sleep(30)
Alternatively you can use the wait_for_completion argument:
>>> with open("/path/to/file", "rb") as f:
>>> analysis = client.scan_file(f, wait_for_completion=True)
When wait_for_completion is True, vt.Client.scan_file() doesn't return until the analysis has been completed. literally just sits there vibing until it's done. β
π Scan an URL π
Scanning a URL is very similar to scanning a file, you just need to use vt.Client.scan_url() instead of vt.Client.scan_file():
>>> analysis = client.scan_url('https://somedomain.com/foo/bar')
π Download a file π
NOTE: This feature is available only for premium users. π
Downloading a file is very simple β you only need to provide the hash and a file-like object where the file's content will be written to. The target file must be opened in "wb" mode:
>>> with open("/path/to/target_file", "wb") as f:
>>> client.download_file("44d88612fea8a8f36de82e1278abb02f", f)
π Start and abort a Retrohunt job π
NOTE: This feature is available only for premium users. π
Create an empty object of type retrohunt_job and set its rules attribute:
>>> job = vt.Object("retrohunt_job")
>>> job.rules = "rule test { condition:false }"
Post the object to the /intelligence/retrohunt_jobs collection:
>>> job = client.post_object("/intelligence/retrohunt_jobs", obj=job)
Notice that job has been replaced with the value returned by vt.Client.post_object(), so now job has an ID and additional attributes. glow-up fr.
>>> job.id
'username-123456789'
>>> job.status
'starting'
With the object identifier you can ask for the job again and see it making progress. Wait for a few seconds and do:
>>> job = client.get_object("/intelligence/retrohunt_jobs/{}", job.id)
The job status should have changed to running:
>>> job.status
'running'
And the progress attribute should show the completion percentage:
>>> job.progress
1.4145595
Let's abort the job:
>>> response = client.post("/intelligence/retrohunt_jobs/{}/abort", job.id)
>>> response.status
200
Here we are using vt.Client.post() instead of vt.Client.post_object() β this is because the /intelligence/retrohunt_jobs/{id}/abort endpoint doesn't expect an object, just a POST request with an empty body. The result from vt.Client.post() is a vt.ClientResponse instance. π¬
π Create a LiveHunt ruleset π
NOTE: This feature is available only for premium users. π
Create an empty object of type hunting_ruleset and set its name and rules attributes:
>>> rs = vt.Object("hunting_ruleset")
>>> rs.name = "My test ruleset"
>>> rs.rules = "rule test { condition:false }"
Post the object to the /intelligence/hunting_rulesets collection:
>>> rs = client.post_object("/intelligence/hunting_rulesets", obj=rs)
Because we didn't set the enabled attribute while creating the ruleset, it was created with enabled=False by default:
>>> rs.enabled
False
Let's enable the ruleset:
>>> rs.enabled = True
>>> rs = client.patch_object("/intelligence/hunting_rulesets/{}", rs.id, obj=rs)
>>> rs.enabled
True
ate. π
π§ Closing π§
Once you're done using the client, call client.close() at the end of your script, to make sure the client is properly closed. Otherwise you might see tracebacks indicating the client was never closed. π don't do that to yourself.
>>> client.close()
If you use a [context manager](https://docs.python.org/3/reference/datamodel.html#context-managers), the client is automatically closed after exiting, so there's no need to call close:
>>> with vt.Client('<apikey>') as client:
... file = client.get_object("/files/44d88612fea8a8f36de82e1278abb02f")
... print(file.type_tag)
text
β API REFERENCE β
πΈ Client πΈ
VT module. (yes, that's the whole line, queen. π)
π exception vt.APIError(code: str, message: str)
Class that encapsules errors returned by the VirusTotal API.
π
class vt.Client(apikey: str, agent: str = 'unknown', host: str | None = None, trust_env: bool = False, timeout: int = 300, proxy: str | None = None, headers: Dict | None = None, verify_ssl: bool = True, connector: BaseConnector = None)
Client for interacting with VirusTotal.
Parameters β okay bestie, here's the rundown:
apikey(str) β Your VirusTotal API key. πagent(str) β A string that identifies your application.host(str) β By defaulthttps://www.virustotal.com, it can be changed for testing purposes.trust_env(bool) β Get proxies information fromHTTP_PROXY/HTTPS_PROXYenvironment variables if the parameter isTrue(Falseby default).timeout(int) β A int that determines the number of seconds to wait for a request to timeout (300by default).proxy(str) β a string that tells the client which proxy to send its requests through (defaults toNone, so like, no proxy).headers(dict) β a dict of headers, defined by you, the user.verify_ssl(bool) β whether or not to verify the certificate on SSL connections.connector(aiohttp.BaseConnector) β (optional) a customaiohttpconnector, if you're fancy like that.
π
close() -> None
Closes the client. Simple as that.
Once you're done with the client, you gotta close it, bestie β that's how the resources (think TCP connections) get released.
Not closing the client after you're done using it might hit you with error tracebacks saying it wasn't properly closed. Not a vibe. π
π
async close_async() -> None
Same energy as close(), except it returns a coroutine. β¨
π
delete()
delete(path: str, *path_args: Any, data: str | bytes | None = None, json_data: Dict | None = None) -> ClientResponse
Sends a DELETE request to whatever API endpoint you point it at.
Parameters
path(str) β the path to the API endpoint, and yes it can contain format placeholders{}.path_argsβ a variable number of arguments that get slotted into any placeholders used inpath.data(a string or bytes) β data sent in the request body.json_data(dict) β a dict containing data to send in the request body as JSON.
Returns
An instance of ClientResponse.
π
async delete_async()
async delete_async(path: str, *path_args: Any, data: str | bytes | None = None, json_data: Dict | None = None) -> ClientResponse
Like delete() but returns a coroutine. Same behavior, just coroutine-flavored. π¦
π
download_file()
download_file(file_hash: str, file: BinaryIO) -> None
Downloads a file given its _ (SHA-256, SHA-1 or MD5).
The file identified by the hash gets written to the file object you hand over. Heads up: that file object must be opened in write binary mode ('wb').
Parameters
file_hash(str) β the file hash.file(file-like object) β a file object that the downloaded file will be written to.
π
async download_file_async()
async download_file_async(file_hash: str, file: BinaryIO) -> None
Like download_file() but returns a coroutine. π«Ά
π
download_zip_files()
download_zip_files(hashes: List[str], zipfile: BinaryIO, password: str | None = None, sleep_time: int = 20) -> None
Creates a bundle zip bundle containing one or multiple files. (Yes, "zip bundle" β iconic phrasing, we keep it.) π
The file identified by the hash gets written to the file object you provide. And again: that file object must be opened in write binary mode ('wb').
Parameters
hashesβ a list of file hashes (SHA-256, SHA-1 or MD5).zipfileβ a file object where the downloaded zip file will be written to.passwordβ optional, a password to protect the zip file. πsleep_timeβ optional, seconds to sleep between each request.
π
feed()
feed(feed_type: FeedType, cursor: str | None = None) -> Feed
Returns an iterator for a VirusTotal feed.
This function returns an iterator that lets you retrieve a continuous stream of files as they're scanned by VirusTotal. For the full rundown, see the documentation for the Feed class.
Parameters
feed_typeβ one of the supported feed types enumerated inFeedType.cursor(str) β an optional cursor indicating where to start. This argument can be a string in the format'YYYMMDDhhmm'indicating the date and time of the first package that will be retrieved.
π
get()
get(path: str, *path_args: Any, params: Dict | None = None) -> ClientResponse
Sends a GET request to a given API endpoint.
This is a low-level function β it returns a raw HTTP response, and honestly? No error checking, no response parsing, none of that. If you want the higher-level glow-up, see get_json(), get_data() and get_object().
Parameters
path(str) β path to the API endpoint, can contain format placeholders{}.path_argsβ a variable number of arguments that are put into any placeholders used inpath.params(dict) β parameters sent in the request.
Returns
An instance of ClientResponse.
π
async get_async()
async get_async(path: str, *path_args: Any, params: Dict | None = None) -> ClientResponse
Like get() but returns a coroutine. β
π
get_data()
get_data(path: str, *path_args: Any, params: Dict | None = None) -> Any
Sends a GET request to a given API endpoint and returns the response's data.
Most VirusTotal API responses are JSON-encoded with this format:
{"data": <response data>}
This function parses the server's response and returns only the data. If the response isn't in the expected format, an exception gets raised β no silent failures here. For endpoints where the data is a VirusTotal object, you can use get_object() instead.
Parameters
path(str) β path to the API endpoint, can contain format placeholders{}.path_argsβ a variable number of arguments that are put into any placeholders used inpath.params(dict) β parameters sent in the request.
Returns
Whatever the server returned in the response's data field. It may be a dict, a list, a string, or some other Python type, depending on the endpoint you called. Surprise! π
π
async get_data_async()
async get_data_async(path: str, *path_args: Any, params: Dict | None = None) -> Any
Like get_data() but returns a coroutine. π
π
async get_error_async()
async get_error_async(response: ClientResponse) -> APIError | None
Given a ClientResponse, returns an APIError.
This function checks whether the response from the VirusTotal backend was an error, and returns the appropriate APIError β or None if no error occurred. Very "vibe check the response" of it. π₯
Parameters
responseβ aClientResponseinstance.
Returns
An instance of APIError or None.
π
get_json()
get_json(path: str, *path_args: Any, params: Dict | None = None) -> Dict
Sends a GET request to a given API endpoint and parses the response.
Most VirusTotal API responses are JSON-encoded, so this function parses the JSON, checks for errors, and returns the server response as a dictionary. The full package. π¦
Parameters
path(str) β path to the API endpoint, can contain format placeholders{}.path_argsβ a variable number of arguments that are put into any placeholders used inpath.params(dict) β parameters sent in the request.
Returns
A dictionary with the backend's response.
π
async get_json_async()
async get_json_async(path: str, *path_args: Any, params: Dict | None = None) -> Dict
Like get_json() but returns a coroutine. π
π
get_object()
get_object(path: str, *path_args: Any, params: Dict | None = None) -> Object
Sends a GET request to a given API endpoint and returns an object.
Lowkey important: the endpoint you specify must return an object, not a collection. So like, get_object works with endpoints like /files/{file_id} and /urls/{url_id} β those return an individual object β but it does not work with /comments, which returns a collection of objects. Read the room. π
Parameters
path(str) β path to the API endpoint, can contain format placeholders{}.path_argsβ a variable number of arguments that are put into any placeholders used inpath.params(dict) β parameters sent in the request.
Returns
An instance of Object.
π
async get_object_async()
async get_object_async(path: str, *path_args: Any, params: Dict | None = None) -> Object
Like get_object() but returns a coroutine. πΈ
π
iterator()
iterator(path: str, *path_args: Any, params: Dict | None = None, cursor: str | None = None, limit: int | None = None, batch_size: int = 0) -> Iterator
Returns an iterator for the collection specified by the given path.
The endpoint specified by path must return a collection of objects. An example of such an endpoint is /comments β and /intelligence/search too. π
Parameters
path(str) β path to the API endpoint returning a collection.path_argsβ a variable number of arguments that are put into any placeholders used inpath.params(dict) β additional parameters passed to the endpoint.cursor(str) β cursor for resuming the iteration at the point it was left previously. A cursor can be obtained withIterator.cursor(). Note that this cursor is not the same one returned by the VirusTotal API. πlimit(int) β maximum number of objects that will be returned by the iterator. If a limit isn't provided, the iterator just keeps going until it reaches the last object in the collection. No cap, literally. πbatch_size(int) β max number of objects pulled on each call to the endpoint. If you don't pass it, the server decides how many objects to hand back. (Let the server cook, honestly.)
Returns An instance of Iterator. π
patch(path: str, *path_args: Any, data: str | bytes | None = None, json_data: Dict | None = None) -> ClientResponse
Sends a PATCH request to a given API endpoint.
Okay so heads up bestie β this is a low-level function. It gives you back a raw HTTP response, and it does zero error checking, zero response parsing. None. Nada. π«Ά If you want the higher-level glow-up version, go look at patch_object().
Parameters
path(str) β path to the API endpoint, and it can contain format placeholders{}.path_argsβ a variable number of arguments that get dropped into whatever placeholders you used inpath. β¨data(a string or bytes) β data sent in the request body.json_data(dict) β adictcontaining data to send in the request body as JSON.
Returns An instance of ClientResponse.
async patch_async(path: str, *path_args: Any, data: str | bytes | None = None, json_data: Dict | None = None) -> ClientResponse
Literally patch() but it returns a coroutine. That's the whole vibe. π
patch_object(path: str, *path_args: Any, obj: Object) -> Object
Sends a PATCH request for modifying an object.
This one actually modifies an object, so the endpoint has to be one that identifies a single object, like /intelligence/hunting_rulesets/{id}. π
Parameters
path(str) β path to the API endpoint, can contain format placeholders{}.path_argsβ a variable number of arguments that are put into any placeholders used inpath.obj(Object) β the object that has been modified.
Returns An instance of Object representing the same object, after the changes have been applied. Slay. β¨
async patch_object_async(path: str, *path_args: Any, obj: Object) -> Object
Like patch_object() but returns a coroutine.
post(path: str, *path_args: Any, data: str | bytes | None = None, json_data: Dict | None = None) -> ClientResponse
Sends a POST request to a given API endpoint.
Again, low-level bestie: raw HTTP response, no error checking, no response parsing. If you want the friendlier one, that's post_object(). π¦
Parameters
path(str) β path to the API endpoint, can contain format placeholders{}.path_argsβ a variable number of arguments that are put into any placeholders used inpath.data(a string or bytes) β data sent in the request body.json_data(dict) β adictcontaining data to send in the request body as JSON.
Returns An instance of ClientResponse.
async post_async(path: str, *path_args: Any, data: str | bytes | None = None, json_data: Dict | None = None) -> ClientResponse
Like post() but returns a coroutine. Same energy, async flavor. π
post_object(path: str, *path_args: Any, obj: Object) -> Object
Sends a POST request for creating an object.
This one creates a brand new object. The endpoint must be one that identifies a collection, like /intelligence/hunting_rulesets. New object, who dis. π
Parameters
path(str) β path to API endpoint.path_argsβ a variable number of arguments that are put into any placeholders used inpath.obj(Object) β instanceObjectwith the type expected by the API endpoint.
Returns An instance of Object representing the new object.
async post_object_async(path: str, *path_args: Any, obj: Object) -> Object
Like post_object() but returns a coroutine.
scan_file(file: BinaryIO, wait_for_completion: bool = False) -> Object
Scans a file.
Parameters
file(file-like object) β file to be scanned.wait_for_completion(bool) β ifTrue, the function doesn't return until the analysis has been completed. It just sits there. Patiently. π§
Returns An instance of Object of analysis type.
async scan_file_async(file: BinaryIO, wait_for_completion: bool = False) -> Object
Like scan_file() but returns a coroutine.
scan_file_private(file: BinaryIO | str, wait_for_completion: bool = False) -> Object
Scan file privately. π€« (Private mode activated, iykyk.)
Args: file: File to scan (path string or file object) wait_for_completion: Wait for completion
Returns: Object: Analysis object with scan results
async scan_file_private_async(file: BinaryIO | str, wait_for_completion: bool = False) -> Object
Async version of scan_file_private.
scan_url(url: str, wait_for_completion: bool = False) -> Object
Scans a URL.
Parameters
url(str) β the URL to be scanned.wait_for_completion(bool) β ifTrue, the function doesn't return until the analysis has been completed.
Returns An instance of Object of analysis type.
async scan_url_async(url: str, wait_for_completion: bool = False) -> Object
Like scan_url() but returns a coroutine.
scan_url_private(url: str, wait_for_completion: bool = False) -> Object
Scans a URL privately. π
Parameters
url(str) β the URL to be scanned.wait_for_completion(bool) β ifTrue, the function doesn't return until the analysis has been completed.
Returns An instance of Object of analysis type.
async scan_url_private_async(url: str, wait_for_completion: bool = False) -> Object
Like scan_url_private() but returns a coroutine.
π class vt.ClientResponse(aiohttp_resp: ClientResponse)
Class representing the HTTP responses returned by the client.
Lowkey this class is just a wrapper around aiohttp.ClientResponse β a thing wrapper, if you will β that lets you use it in both asynchronous and synchronous mode. π«Ά
Instances of this class have all the attributes you can find in aiohttp.ClientResponse, like version, status, method, url, and so on.
And here's the cute part: methods in aiohttp.ClientResponse that return a coroutine have two flavors in this class β synchronous and asynchronous. For example, aiohttp.ClientResponse.read() becomes vt.ClientResponse.read_async(), and vt.ClientResponse.read() is the synchronous version of vt.ClientResponse.read_async().
Find more information about attributes and methods in aiohttp.ClientResponse in:
πΈ Feed πΈ
NOTE: This feature is available only for premium users. π (Premium girlies only, sorry bestie.)
VT module.
π¦ class vt.Feed(client: Client, feed_type: FeedType, cursor: str | None = None)
Feed represents a stream of objects received from VirusTotal in real-time. It's giving live updates. π₯
For more information about VirusTotal Feeds see:
In the example below the loop iterates forever, retrieving file objects as they are processed by VirusTotal. For a more elaborate example see the file examples/file_feed.py in this repository.
>>> with vt.Client(<apikey>) as client:
>>> for file_obj in client.feed(vt.FeedType.FILES):
>>> print(file_obj.id)
Instances of this class are not created directly β you should use the vt.Client.feed() method instead. Periodt. β¨
property cursor: str
Returns a cursor indicating the last item retrieved from the feed.
This cursor can be used for creating a new Feed object that continues where a previous one left off. It's literally a bookmark for your feed, iconic. π
π class vt.FeedType(*values)
Feed types.
FILES = 'files'FILE_BEHAVIOURS = 'file-behaviours'URLS = 'urls'
β Object β
VT module.
π class vt.Object(obj_type: str, obj_id: str | None = None, obj_attributes: Dict | None = None)
This class encapsulates any type of object in the VirusTotal API. Any. Type. Bestie. π
Instances of this class are usually obtained from calls to vt.Client.get_object(). However, you need to instantiante this class yourself for creating new objects that will be sent to the backend in a call to vt.Client.post_object().
Learn more about objects in the VirusTotal API in:
π§ classmethod from_dict(obj_dict: Dict)
Creates an object from its dictionary representation.
The dictionary representation of a VirusTotal API object has the following structure:
{
"type": <object type>,
"id": <object id>,
"links": {
"self": "https://www.virustotal.com/api/v3/<collection name>/<obj id>"
},
"attributes": {
...
}
}
At least type and id are required to be present in the dictionary. If they're not there, an exception is raised. No type and id? No object. π
π get(attr_name: str, default: Any | None = None) -> Any
Returns an attribute by name.
If the attribute isn't actually there on the object, it hands you back None, or whatever you passed in as the default argument. π
Parameters
attr_name(str) β the name of the attribute, bestie. that's it. that's the whole param.defaultβ an optional value that gets returned if the attribute isn't present in the object.
set_data(key: str, value: Any) β sets a field of the object's data. simple as that. β¨
π¦ Iterator π¦
VirusTotal module, as always. π«Ά
class vt.Iterator(client: Client, path: str, params=None, cursor: str | None = None, limit: int | None = None, batch_size: int = 0)
ngl this one's a whole vibe β Iterator is literally your girl for looping over object collections. π
Some endpoints in the VirusTotal API represent a collection of objects, like for example:
/files/{id}/comments
/intelligence/search
And you can iterate over all of those collections with an instance of this class. slay.
Wanna learn more about collections in the VirusTotal API? It's right here: https://docs.virustotal.com/reference/collections-create
So this example iterates over the most recent 200 comments, pulling them in batches of 20:
>>> client = vt.Client(<apikey>)
>>> it = client.iterator('/comments', batch_size=20, limit=200)
>>> for comment in it:
>>> print(comment.text)
>>> print(it.cursor)
When the iteration is done, it prints the iterator's cursor. and that cursor is your little time machine β you can use it to create another iterator that picks up exactly where the previous one left off. no repeats, no gaps, iconic behaviour fr fr. β
The Iterator class also exposes an async iterator, because of course she does:
>>> # Define an async coroutine that iterates over the comments.
>>> async def print_comments():
>>> async for comment in client.iterator('/comments', limit=200):
>>> print(comment.id)
>>> # Run the print_comments coroutine using asyncio
>>> import asyncio
>>> asyncio.get_event_loop().run_until_complete(print_comments)
π cursor (property): str | None
Cursor indicating the last returned object. π
You can use it to create a brand new iterator that continues where the current one left off β like a bookmark but for API results, iykyk.
π meta_async (property): Dict
Meta information. πΈ
The cursor is not included in here, because it's already exposed as its own property. she's independent like that. π
π§ Utility functions π§
VirusTotal module. again. hi. π«Ά
vt.url_id(url: str) -> str
Generates the object ID for an URL. β¨
The ID it generates can be used in calls that expect a URL ID, like this one:
client.get_object('/urls/<id>')
so you don't have to hand-craft IDs yourself, because nobody has time for that. π
π AUTHOR π
VirusTotal
π COPYRIGHT π
2023, VirusTotal
okay bestie that's the whole page!! π¦πβ¨ You just read VT-PY(1) β source/version VT-PY(1), dated Mar 27, 2026 β and honestly? you ate. go build something cute with it. periodt. π
πΈπ
π¦ where this page comes from
- alpine 3.24: py3-vt-py-doc 0.22.0-r1 Β· Apache-2.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms