x-apikey header — not a bearer token — and every call is against https://www.virustotal.com/api/v3.
- The key header is
x-apikey. Without it the API answersAuthenticationRequiredError; with a wrong one,WrongCredentialsError. Both are 401, and both mean the credential needs fixing. - Two different 429s, and only one is retryable.
TooManyRequestsErroris a burst that clears, and the plugin retries it up to three times with backoff.QuotaExceededErrormeans a minute, daily or monthly allowance is spent — retrying only burns another request against a quota that is already gone, so it has a separateQUOTA_ERRORhandler with no retries. NotAvailableYetis a 400, not a 404. A freshly submitted file or URL exists but has not been processed, so it answers400 NotAvailableYetrather than404 NotFoundError. The plugin keeps them apart: one means wait, the other means the indicator is unknown.- Scans answer an id, not a verdict.
urls.scanand every rescan return an analysis descriptor; pollanalyses.getuntil itsstatusiscompleted. Aqueuedanalysis is a normal result, not a failure. - A URL is addressed by an identifier, not the raw URL.
urls.gettakes either the unpadded base64url form (RFC 4648 §3.2), whichurlId()builds, or the SHA-256ida URL report returns. The base64url form decodes back to the full URL, so a lookup sends VirusTotal the whole address, query string and any token in it included. Strip secrets before looking a URL up. urls.scanis the one form-encoded route. It takesurl=as a form field rather than a JSON body, which is why the plugin names the media type for that call.- Commenting and voting use a JSON:API envelope. The body is
{ data: { type: 'comment', attributes: { text } } }or{ data: { type: 'vote', attributes: { verdict } } }, not a plain field map. A word starting with#in a comment becomes a tag. - A vote is
harmlessormalicious— there is no abstention, and no way to change a vote other than voting again. - Submitting an indicator publishes it. VirusTotal’s docs are explicit that anything scanned or queried through the API joins the public dataset, so
urls.scancannot be withdrawn — and the plugin cannot undo it either. - Paging is
meta.cursor. Lists answer{ data, meta: { cursor } }rather than anextlink, andlimitis capped at 40. - Reports are large and open. A file report carries over a hundred attributes and a per-engine verdict map; the plugin names the common ones and passes the rest through rather than pretending to model them all.
- 19 typed API operations
Setup
1
Install
2
Add the plugin
corsair.ts
multiTenancy: true, scope calls with corsair.withTenant(id). See Quick start for KEK + Hub keys, and Multi-tenancy for account isolation.3
Choose authentication
- API Key (Recommended)
No setup required yet. When you make your first request as a tenant, Corsair prompts for the API key.More: API Key
4
Connect a tenant
Mint a connect link and send the tenant to it. Hub hosts the page and delivers the result to your app — see Connect / OAuth.
Example API calls
Read a file reportWhat’s next
API reference
Every
virustotal.api.* operation with input and output types.Connect / OAuth
createLink, Hub delivery, and tenant connect flows.
Use with agents
Expose this plugin’s operations as MCP tools.