VirusTotal · API Governance Rules

VirusTotal API Rules

Spectral linting rules defining API design standards and conventions for VirusTotal.

40 Rules error 15 warn 19 info 6
View Rules File View on GitHub

Rule Categories

vt

Rules

error
vt-info-title-required
Spec title must be present and start with "VirusTotal".
$.info.title
error
vt-info-version-required
Spec version must be declared and start with "3".
$.info.version
warn
vt-info-description-required
Spec description must be present and non-trivial.
$.info.description
warn
vt-info-contact-required
Spec must declare a contact (VirusTotal / GTI).
$.info.contact
warn
vt-info-license-required
Spec must declare a license (VirusTotal Terms of Service).
$.info.license
error
vt-openapi-version
Spec must be OpenAPI 3.x.
$.openapi
error
vt-servers-required
At least one server URL must be defined.
$.servers
error
vt-servers-https
Server URL must use HTTPS.
$.servers[*].url
warn
vt-servers-api-v3-host
Server URL should point at api/v3 on virustotal.com or gtidocs subdomain.
$.servers[*].url
warn
vt-paths-snake-case
Path segments use snake_case (VirusTotal convention) — letters, digits, underscores, hyphens, and {param} placeholders only.
$.paths.*~
error
vt-paths-no-trailing-slash
Paths must not end with a trailing slash.
$.paths.*~
error
vt-paths-no-query-string
Path keys must not contain '?' query strings.
$.paths.*~
error
vt-operation-summary-required
Every operation must have a summary.
$.paths.*.[get,post,put,patch,delete].summary
warn
vt-operation-summary-virustotal-prefix
Operation summaries must start with "VirusTotal " (per title-case-summaries skill).
$.paths.*.[get,post,put,patch,delete].summary
warn
vt-operation-description-required
Every operation must have a description.
$.paths.*.[get,post,put,patch,delete]
error
vt-operation-operationid-required
Every operation must have an operationId.
$.paths.*.[get,post,put,patch,delete]
warn
vt-operation-operationid-camelcase
operationId should be camelCase.
$.paths.*.[get,post,put,patch,delete].operationId
error
vt-operation-tags-required
Every operation must have at least one tag.
$.paths.*.[get,post,put,patch,delete].tags
info
vt-operation-microcks-extension
Operations should declare x-microcks-operation for Microcks mocking.
$.paths.*.[get,post,put,patch,delete]
warn
vt-tags-global-defined
Spec must define a global tags array.
$.tags
info
vt-tags-virustotal-grouping
Tag names should follow the VirusTotal grouping convention: " - " (e.g. "IoC Investigation - Files", "YARA Hunting - Livehunt", "Access Control - User Management", "Threat Graphs", "Threat Landscape & Vulnerability Intelligence & Reports & Analysis").
$.tags[*].name
warn
vt-parameter-description-required
Every parameter must have a description.
$.paths.*.[get,post,put,patch,delete].parameters[*]
warn
vt-parameter-snake-case
Parameter names should be snake_case (VirusTotal convention).
$.paths.*.[get,post,put,patch,delete].parameters[*].name
error
vt-parameter-no-apikey-as-query
API key must be sent via the x-apikey header, not as a query parameter.
$.paths.*.[get,post,put,patch,delete].parameters[?(@.in=='query')].name
info
vt-parameter-pagination-cursor
List endpoints prefer cursor-based pagination via 'limit' + 'cursor'.
$.paths.*.get.parameters[?(@.name=='page' || @.name=='offset')]
warn
vt-requestbody-json-content
Request bodies must offer application/json.
$.paths.*.[post,put,patch].requestBody.content
error
vt-response-success-required
Every operation must declare at least one 2xx response.
$.paths.*.[get,post,put,patch,delete].responses
warn
vt-response-401-defined
Operations should document the 401 response (missing/invalid API key).
$.paths.*.[get,post,put,patch,delete].responses
warn
vt-response-429-defined
Operations should document the 429 response (rate limit / quota exceeded).
$.paths.*.[get,post,put,patch,delete].responses
warn
vt-response-json-content
2xx responses must be served as application/json.
$.paths.*.[get,post,put,patch,delete].responses[?(@property.match(/^2\d\d$/))].content
warn
vt-schema-property-snake-case
Schema property names should be snake_case (VirusTotal convention).
$.components.schemas.*.properties.*~
info
vt-schema-object-shape
VirusTotal objects follow a JSON:API-ish shape: { id, type, attributes, relationships }. Any schema whose name ends with "Object" should declare those four properties.
$.components.schemas[?(@property.match(/Object$/))]
info
vt-schema-data-envelope
A DataEnvelope schema should be present to model the {"data": ...} response wrapper.
$.components.schemas
info
vt-schema-error-envelope
An ErrorResponse schema should be present to model {"error": {code, message}}.
$.components.schemas
error
vt-security-global-defined
Spec must declare a global security requirement.
$.security
error
vt-security-scheme-vtapikey
The VTApiKey security scheme (apiKey in header named x-apikey) must be defined.
$.components.securitySchemes.VTApiKey
error
vt-get-no-requestbody
GET operations must not declare a requestBody.
$.paths.*.get.requestBody
warn
vt-delete-no-requestbody
DELETE operations should not declare a requestBody.
$.paths.*.delete.requestBody
warn
vt-no-empty-description
Descriptions must not be empty strings.
$..description
warn
vt-deprecation-documented
Deprecated operations should explain the deprecation in the description.
$.paths.*[?(@.deprecated==true)]

Spectral Ruleset

Raw ↑
# VirusTotal API v3 — Spectral ruleset
#
# Enforces the conventions observed across the VirusTotal / Google Threat Intelligence
# API v3 specs: snake_case paths, x-apikey header auth, "data" envelope responses,
# tag prefixes (`IoC Investigation - ...`, `YARA Hunting - ...`), and the JSON:API-ish
# object shape (id + type + attributes + relationships).
#
# Usage:
#   spectral lint --ruleset rules/virustotal-rules.yml openapi/*.yml

extends:
  - spectral:oas

rules:

  # ============================================================
  # INFO / METADATA
  # ============================================================

  vt-info-title-required:
    description: Spec title must be present and start with "VirusTotal".
    message: "{{property}} must start with 'VirusTotal'"
    severity: error
    given: $.info.title
    then:
      function: pattern
      functionOptions:
        match: "^VirusTotal\\b"

  vt-info-version-required:
    description: Spec version must be declared and start with "3".
    message: "info.version must start with '3' (VirusTotal API v3)."
    severity: error
    given: $.info.version
    then:
      function: pattern
      functionOptions:
        match: "^3"

  vt-info-description-required:
    description: Spec description must be present and non-trivial.
    message: "info.description must be a substantive paragraph (min 40 chars)."
    severity: warn
    given: $.info.description
    then:
      function: length
      functionOptions:
        min: 40

  vt-info-contact-required:
    description: Spec must declare a contact (VirusTotal / GTI).
    severity: warn
    given: $.info.contact
    then:
      function: truthy

  vt-info-license-required:
    description: Spec must declare a license (VirusTotal Terms of Service).
    severity: warn
    given: $.info.license
    then:
      function: truthy

  # ============================================================
  # OPENAPI VERSION + SERVERS
  # ============================================================

  vt-openapi-version:
    description: Spec must be OpenAPI 3.x.
    severity: error
    given: $.openapi
    then:
      function: pattern
      functionOptions:
        match: "^3\\."

  vt-servers-required:
    description: At least one server URL must be defined.
    severity: error
    given: $.servers
    then:
      function: length
      functionOptions:
        min: 1

  vt-servers-https:
    description: Server URL must use HTTPS.
    severity: error
    given: $.servers[*].url
    then:
      function: pattern
      functionOptions:
        match: "^https://"

  vt-servers-api-v3-host:
    description: Server URL should point at api/v3 on virustotal.com or gtidocs subdomain.
    severity: warn
    given: $.servers[*].url
    then:
      function: pattern
      functionOptions:
        match: "/api/v3$"

  # ============================================================
  # PATHS — NAMING CONVENTIONS
  # ============================================================

  vt-paths-snake-case:
    description: Path segments use snake_case (VirusTotal convention) — letters, digits, underscores, hyphens, and {param} placeholders only.
    message: "Path '{{value}}' must use snake_case segments."
    severity: warn
    given: $.paths.*~
    then:
      function: pattern
      functionOptions:
        match: "^(/[a-z0-9_]+(-[a-z0-9_]+)*|/\\{[a-zA-Z_]+\\})+$"

  vt-paths-no-trailing-slash:
    description: Paths must not end with a trailing slash.
    severity: error
    given: $.paths.*~
    then:
      function: pattern
      functionOptions:
        notMatch: "/$"

  vt-paths-no-query-string:
    description: Path keys must not contain '?' query strings.
    severity: error
    given: $.paths.*~
    then:
      function: pattern
      functionOptions:
        notMatch: "\\?"

  # ============================================================
  # OPERATIONS
  # ============================================================

  vt-operation-summary-required:
    description: Every operation must have a summary.
    severity: error
    given: $.paths.*.[get,post,put,patch,delete].summary
    then:
      function: truthy

  vt-operation-summary-virustotal-prefix:
    description: Operation summaries must start with "VirusTotal " (per title-case-summaries skill).
    message: "Summary should be prefixed with 'VirusTotal '."
    severity: warn
    given: $.paths.*.[get,post,put,patch,delete].summary
    then:
      function: pattern
      functionOptions:
        match: "^VirusTotal "

  vt-operation-description-required:
    description: Every operation must have a description.
    severity: warn
    given: $.paths.*.[get,post,put,patch,delete]
    then:
      field: description
      function: truthy

  vt-operation-operationid-required:
    description: Every operation must have an operationId.
    severity: error
    given: $.paths.*.[get,post,put,patch,delete]
    then:
      field: operationId
      function: truthy

  vt-operation-operationid-camelcase:
    description: operationId should be camelCase.
    message: "operationId '{{value}}' should be camelCase (no dashes, underscores, or spaces)."
    severity: warn
    given: $.paths.*.[get,post,put,patch,delete].operationId
    then:
      function: pattern
      functionOptions:
        match: "^[a-z][a-zA-Z0-9]*$"

  vt-operation-tags-required:
    description: Every operation must have at least one tag.
    severity: error
    given: $.paths.*.[get,post,put,patch,delete].tags
    then:
      function: length
      functionOptions:
        min: 1

  vt-operation-microcks-extension:
    description: Operations should declare x-microcks-operation for Microcks mocking.
    severity: info
    given: $.paths.*.[get,post,put,patch,delete]
    then:
      field: x-microcks-operation
      function: truthy

  # ============================================================
  # TAGS
  # ============================================================

  vt-tags-global-defined:
    description: Spec must define a global tags array.
    severity: warn
    given: $.tags
    then:
      function: length
      functionOptions:
        min: 1

  vt-tags-virustotal-grouping:
    description: |
      Tag names should follow the VirusTotal grouping convention:
      "<Area> - <Subject>" (e.g. "IoC Investigation - Files",
      "YARA Hunting - Livehunt", "Access Control - User Management",
      "Threat Graphs", "Threat Landscape & Vulnerability Intelligence & Reports & Analysis").
    message: "Tag '{{value}}' should follow the '<Area> - <Subject>' or '<Area>' format used across VirusTotal v3."
    severity: info
    given: $.tags[*].name
    then:
      function: pattern
      functionOptions:
        match: "^(Access Control|IoC Feeds|IoC Investigation|Private Scanning|YARA Hunting|Threat Graphs|Threat Landscape)( - .+| & .+)?$"

  # ============================================================
  # PARAMETERS
  # ============================================================

  vt-parameter-description-required:
    description: Every parameter must have a description.
    severity: warn
    given: $.paths.*.[get,post,put,patch,delete].parameters[*]
    then:
      field: description
      function: truthy

  vt-parameter-snake-case:
    description: Parameter names should be snake_case (VirusTotal convention).
    message: "Parameter '{{value}}' should be snake_case."
    severity: warn
    given: $.paths.*.[get,post,put,patch,delete].parameters[*].name
    then:
      function: pattern
      functionOptions:
        match: "^([a-z][a-z0-9_]*|x-[a-z][a-z0-9-]*)$"

  vt-parameter-no-apikey-as-query:
    description: API key must be sent via the x-apikey header, not as a query parameter.
    message: "API key parameter '{{value}}' must be in the x-apikey header, not the query string."
    severity: error
    given: $.paths.*.[get,post,put,patch,delete].parameters[?(@.in=='query')].name
    then:
      function: pattern
      functionOptions:
        notMatch: "^(apikey|api_key|apiKey)$"

  vt-parameter-pagination-cursor:
    description: List endpoints prefer cursor-based pagination via 'limit' + 'cursor'.
    severity: info
    given: $.paths.*.get.parameters[?(@.name=='page' || @.name=='offset')]
    then:
      function: undefined

  # ============================================================
  # REQUEST BODIES
  # ============================================================

  vt-requestbody-json-content:
    description: Request bodies must offer application/json.
    severity: warn
    given: $.paths.*.[post,put,patch].requestBody.content
    then:
      field: application/json
      function: truthy

  # ============================================================
  # RESPONSES
  # ============================================================

  vt-response-success-required:
    description: Every operation must declare at least one 2xx response.
    severity: error
    given: $.paths.*.[get,post,put,patch,delete].responses
    then:
      function: schema
      functionOptions:
        schema:
          type: object
          patternProperties:
            "^2[0-9][0-9]$": {}
          required: []
          minProperties: 1

  vt-response-401-defined:
    description: Operations should document the 401 response (missing/invalid API key).
    severity: warn
    given: $.paths.*.[get,post,put,patch,delete].responses
    then:
      field: "401"
      function: truthy

  vt-response-429-defined:
    description: Operations should document the 429 response (rate limit / quota exceeded).
    severity: warn
    given: $.paths.*.[get,post,put,patch,delete].responses
    then:
      field: "429"
      function: truthy

  vt-response-json-content:
    description: 2xx responses must be served as application/json.
    severity: warn
    given: $.paths.*.[get,post,put,patch,delete].responses[?(@property.match(/^2\d\d$/))].content
    then:
      field: application/json
      function: truthy

  # ============================================================
  # SCHEMAS — PROPERTY NAMING
  # ============================================================

  vt-schema-property-snake-case:
    description: Schema property names should be snake_case (VirusTotal convention).
    message: "Property '{{property}}' should be snake_case."
    severity: warn
    given: $.components.schemas.*.properties.*~
    then:
      function: pattern
      functionOptions:
        match: "^[a-z][a-z0-9_]*$"

  vt-schema-object-shape:
    description: |
      VirusTotal objects follow a JSON:API-ish shape: { id, type, attributes, relationships }.
      Any schema whose name ends with "Object" should declare those four properties.
    severity: info
    given: "$.components.schemas[?(@property.match(/Object$/))]"
    then:
      field: properties
      function: schema
      functionOptions:
        schema:
          type: object
          required: [id, type, attributes]

  vt-schema-data-envelope:
    description: 'A DataEnvelope schema should be present to model the {"data": ...} response wrapper.'
    severity: info
    given: $.components.schemas
    then:
      field: DataEnvelope
      function: truthy

  vt-schema-error-envelope:
    description: 'An ErrorResponse schema should be present to model {"error": {code, message}}.'
    severity: info
    given: $.components.schemas
    then:
      field: ErrorResponse
      function: truthy

  # ============================================================
  # SECURITY
  # ============================================================

  vt-security-global-defined:
    description: Spec must declare a global security requirement.
    severity: error
    given: $.security
    then:
      function: length
      functionOptions:
        min: 1

  vt-security-scheme-vtapikey:
    description: The VTApiKey security scheme (apiKey in header named x-apikey) must be defined.
    severity: error
    given: $.components.securitySchemes.VTApiKey
    then:
      function: schema
      functionOptions:
        schema:
          type: object
          required: [type, in, name]
          properties:
            type:
              const: apiKey
            in:
              const: header
            name:
              const: x-apikey

  # ============================================================
  # HTTP METHOD CONVENTIONS
  # ============================================================

  vt-get-no-requestbody:
    description: GET operations must not declare a requestBody.
    severity: error
    given: $.paths.*.get.requestBody
    then:
      function: falsy

  vt-delete-no-requestbody:
    description: DELETE operations should not declare a requestBody.
    severity: warn
    given: $.paths.*.delete.requestBody
    then:
      function: falsy

  # ============================================================
  # GENERAL QUALITY
  # ============================================================

  vt-no-empty-description:
    description: Descriptions must not be empty strings.
    severity: warn
    given: "$..description"
    then:
      function: truthy

  vt-deprecation-documented:
    description: Deprecated operations should explain the deprecation in the description.
    severity: warn
    given: "$.paths.*[?(@.deprecated==true)]"
    then:
      field: description
      function: truthy