VIRUSSIGN
DEVELOPER RESOURCES / API V1

Build with VirusSign.

From your first IOC lookup to connected investigations. Practical examples, clear request parameters and response guidance.

01 / GET STARTED

Your first query

Request an API key and confirm its permissions with VirusSign. Use the public API base URL below. Use your own issued key in the examples.

Base URL
https://api.virussign.com

Set VIRUSSIGN_API_KEY in your local environment, then query an indicator. The examples use example.com as a placeholder.

cURL · domain lookup
curl --get "https://api.virussign.com/v1/ioc/domains/example.com" \
  --header "x-api-key: $VIRUSSIGN_API_KEY"
Python · domain lookup
import os
import requests  # pip install requests

response = requests.get(
    "https://api.virussign.com/v1/ioc/domains/example.com",
    headers={"x-api-key": os.environ["VIRUSSIGN_API_KEY"]},
    timeout=(5, 30),
)
if response.status_code == 404:
    print("No available record; this does not mean safe.")
else:
    response.raise_for_status()
    result = response.json()
    print(result.get("indicator", {}))
A 404 means that no matching record is available to this request. It does not establish that an indicator is safe. Examples are read-only lookups; they do not submit files or URLs for new analysis.

Use the OpenAPI specification

The OpenAPI 3.1 specification (JSON) describes the API’s endpoints, parameters, authentication and response structures. Import it into an API client such as Postman to prepare requests, or use it with compatible tools to generate integration code. It contains no intelligence records or API keys.

Get started in Postman

  1. Download the JSON file using Download OpenAPI.
  2. In Postman, select Import and choose the file. Import it as a collection, or generate a collection from the imported specification.
  3. Check that the API base URL is https://api.virussign.com. Set the collection’s API Key authorization header to x-api-key and use a local secret variable for your issued key.
  4. Choose an endpoint, replace its example or placeholder indicator, and send the request. The request must be within your key’s permissions and uses your API quota.

Downloading or importing the specification does not make API requests or grant access. You can also follow the cURL and Python examples directly without downloading it. Generated clients may need adjustments for optional response fields and pagination.

Authentication & access

You can also connect your API key to this website to investigate indicators using your account’s permissions and allowance. Website queries and direct API requests share the same quota.

Send your API key in the x-api-key HTTP header over HTTPS. Keep the key in server-side configuration or an environment variable. Do not embed it in public JavaScript, page URLs or source control.

AccessBehavior
IOC and relationship queriesUse /v1/ioc/… with your issued API key. Available fields depend on the indicator, requested view and your account permissions.
Feed eventsUse /v1/feed/events with a key that has feed permission. IOC access alone does not include feed access.
Trial scopeThe approval message defines permissions, field availability, quota and expiry. Some fields may be filtered for evaluation accounts.

The public website and the API are separate access surfaces. Website access does not grant permission to redistribute intelligence or integrate it into a commercial service.

IOC lookup

Query one indicator per request. A successful response contains meta and indicator. To process a list, send individual requests with bounded concurrency.

IOC typeStandard routeInput
FileGET /v1/ioc/files/{ioc_key}MD5, SHA-1 or SHA-256 hash
DomainGET /v1/ioc/domains/{ioc_key}Domain name, without a URL scheme or path
IPGET /v1/ioc/ips/{ioc_key}IP address
URLGET /v1/ioc/urls?q={encoded_url}Complete URL encoded as a query parameter
ParameterSuggested usageMeaning
viewOptionalOmit for the service default. compact requests reduced detail; full requests more context; full+relations also requests related objects. URL-encode the plus sign.
limitStart with 10; recommended 1–50Requested page size per relationship, not a combined total across all relationship types. Use the returned cursor to retrieve more items.

Choose a response view

Most examples omit view and follow the service default. Use view=compact when a smaller set of fields is sufficient, for example when reviewing many related indicators. The exact fields and size depend on the indicator type and your access. Request full or full+relations only when you need additional context.

cURL · compact domain lookup
curl --get "https://api.virussign.com/v1/ioc/domains/example.com" \
  --header "x-api-key: $VIRUSSIGN_API_KEY" \
  --data-urlencode "view=compact"

To include relationships in an individual lookup, request them explicitly and start with a small limit:

cURL · file with relationships
curl --get "https://api.virussign.com/v1/ioc/files/YOUR_SHA256" \
  --header "x-api-key: $VIRUSSIGN_API_KEY" \
  --data-urlencode "view=full+relations" \
  --data-urlencode "limit=10"

Query a complete URL

Encode the complete URL with your HTTP client. Place q last in the query string, as shown below, and preserve the original URL’s case, path and query parameters.

cURL · URL lookup
curl --get "https://api.virussign.com/v1/ioc/urls" \
  --header "x-api-key: $VIRUSSIGN_API_KEY" \
  --data-urlencode "q=https://example.com/path?a=1&b=2"
Python · URL lookup
response = requests.get(
    "https://api.virussign.com/v1/ioc/urls",
    headers={"x-api-key": os.environ["VIRUSSIGN_API_KEY"]},
    params={"q": "https://example.com/path?a=1&b=2"},
    timeout=(5, 30),
)
response.raise_for_status()

For subsequent URL relationship requests, use the opaque URL identifier in the returned canonical link when available. Do not assume a URL identifier is a file hash or reconstruct it from a different normalization algorithm.

Responses & data fields

Illustrative response. Available fields vary by IOC type, access and view.

JSON · illustrative response
{
  "meta": {
    "request_id": "example-request-id",
    "timestamp": "2026-09-30T00:00:00Z",
    "quota_remaining": 9999,
    "took_ms": 120
  },
  "indicator": {
    "type": "file",
    "value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "filenames": [
      "illustrative-sample.exe"
    ],
    "analysis": {
      "malicious": true
    },
    "first_seen": "2026-09-01T00:00:00Z",
    "last_seen": "2026-09-29T00:00:00Z"
  }
}
FieldMeaning
meta.request_idRequest identifier for support and correlation.
meta.timestampResponse timestamp in UTC.
meta.quota_remainingRemaining balance or cycle allowance when provided.
meta.took_msServer-reported processing duration in milliseconds.
indicator.typeIndicator type: file, ip, domain or url.
indicator.valueCanonical indicator value; file records normally use SHA-256.
indicator.analysis.maliciousMalicious classification flag; false is not a guarantee of safety.
indicator.analysis.severitySeverity classification when applicable and available.
indicator.analysis.confidenceConfidence signal when available; not a calibrated probability.
indicator.analysis.familyFamily attribution when available.
indicator.analysis.detectionsDetection ratio when available.
indicator.first_seen / last_seenFirst/last observation represented by this record; not necessarily the start or end of malicious activity.
indicator.relationsOptional groups of related objects when requested.
items / has_next / next_cursorStandalone relationship result items and pagination metadata when available.

Type-specific context

IndicatorExamples of additional fields
Filemd5, sha1, sha256, filenames, filesize, filetype, tags and observation timestamps.
IPIP version, country, tags, analysis and observation timestamps.
DomainDomain identity, tld, tags, analysis and observation timestamps.
URLURL identity, host, scheme, port, path_qs, tags, analysis and observation timestamps.
Handle missing optional fields and accept additional fields. Missing data is not a safe verdict; related indicators need their own assessment.

Relationships & pagination

GET/v1/ioc/{collection}/{ioc_key}/{relation}

Replace each placeholder with the value for your request:

PlaceholderValue
collectionSource IOC collection: files, ips, domains or urls.
ioc_keySource indicator: a file hash, IP address, domain name or URL identifier. See IOC lookup for accepted identifiers.
relationA supported relationship name from the table below, such as related_domains.

Relationship queries accept cursor, limit and view. Choose a relationship supported by the source indicator type below. Start with limit=10; for initial integrations, keep pages at 50 or fewer and contact us before requesting larger pages.

RelationRelated objectSource IOC types
related_filesRelated filesips, domains, urls
related_ipsIP addressesfiles, domains, urls
related_domainsDomainsfiles, ips, urls
related_urlsURLsfiles, ips, domains

Available relationships vary by API key tier. Pro and Advanced access offer a broader range of relationship intelligence. Request API access to explore the options.

Not every relation applies to every IOC or permission level. A supported relation may have no items; an unsupported relation can return 404. Follow the returned next_cursor while has_next is true. Keep the IOC, relation, view and other filters unchanged while paging. Treat the cursor as opaque.

cURL · related domains
curl --get "https://api.virussign.com/v1/ioc/files/YOUR_SHA256/related_domains" \
  --header "x-api-key: $VIRUSSIGN_API_KEY" \
  --data-urlencode "limit=10"
Python · process relationship pages
import os
import requests

endpoint = "https://api.virussign.com/v1/ioc/files/YOUR_SHA256/related_domains"
params = {"view": "compact", "limit": 10}
seen_cursors = set()
with requests.Session() as session:
    session.headers["x-api-key"] = os.environ["VIRUSSIGN_API_KEY"]
    for _ in range(10):  # Explicit evaluation page budget.
        response = session.get(endpoint, params=params, timeout=(5, 30))
        response.raise_for_status()
        page = response.json()
        for item in page.get("items", []):
            print(item)  # Replace with idempotent processing.
        if not page.get("has_next"):
            break
        cursor = page.get("next_cursor")
        if not cursor or cursor in seen_cursors:
            raise RuntimeError("Missing or repeated pagination cursor")
        seen_cursors.add(cursor)
        params["cursor"] = cursor
        # Persist this cursor only after processing the page.
    else:
        print("Page budget reached; save the cursor before continuing.")

Record your checkpoint only after successfully processing a page. Make ingestion idempotent so that retrying a page does not create duplicate records. Embedded relationship lists in a detail response can be truncated; use standalone paging for additional results.

Feed events

GET/v1/feed/events

Keep your local intelligence up to date with additions, changes and revocations. Feed access requires an API key with feed permission.

Explore a feed sample before integrating. No API key required.

Choose a time window

For your first request, supply since or until. We recommend starting with a recent since and an explicit limit=50. Use since and until together for a completed window, or omit until for ongoing polling.

Use ISO 8601 timestamps with a timezone and whole seconds, such as 2026-10-04T00:00:00Z. The start is inclusive and the end is exclusive. These bounds apply to the feed event time, rather than the indicator's first or last observation.

cURL · feed time window
curl --get "https://api.virussign.com/v1/feed/events" \
  --header "x-api-key: $VIRUSSIGN_API_KEY" \
  --data-urlencode "mode=events" \
  --data-urlencode "types=file,host,url" \
  --data-urlencode "since=2026-10-04T00:00:00Z" \
  --data-urlencode "until=2026-10-04T01:00:00Z" \
  --data-urlencode "limit=50"
ParameterUsage
modeevents returns additions, updates and revocations. snapshot returns current, unexpired upsert entries within the requested time window.
typesComma-separated file, host and/or url. host includes both domains and IP addresses. Defaults to all three.
sinceInclusive start time. Use a timezone-aware ISO 8601 timestamp with whole seconds, for example 2026-10-04T00:00:00Z.
untilExclusive end time, later than since. Omit for ongoing polling.
cursorOpaque continuation cursor. Use the returned value unchanged with the same account, mode, format and types. The cursor retains the time window; omit it to start a new window.
limitMaximum events per page. Start with 50 and set it explicitly to control page size. Charges use the actual number returned.
formatnative returns JSON with meta and items. stix returns a STIX 2.1 bundle with paging information in response headers.
viewcompact returns core event information. full adds available context; native full responses also include STIX identifiers and patterns.

Read a native JSON page

The default format returns meta and items. Events arrive in feed timestamp order. Apply upsert to add or update an indicator and revoke to withdraw it from your active feed dataset. Use each event's id for deduplication.

JSON · illustrative feed page
{
  "meta": {
    "request_id": "example-feed-request",
    "timestamp": "2026-10-04T01:05:00Z",
    "limit": 50,
    "returned": 1,
    "has_next": false,
    "next_cursor": "OPAQUE_CHECKPOINT",
    "quota_remaining": 999,
    "took_ms": 80
  },
  "items": [
    {
      "id": "event--c65a54c0-1d37-54d0-b5c3-4700e3e0c7cc",
      "timestamp": "2026-10-04T00:30:00Z",
      "action": "upsert",
      "reason": "CTX",
      "indicator": {
        "type": "domain",
        "value": "example.com"
      },
      "analysis": {
        "malicious": true,
        "confidence": 70
      },
      "validity": {
        "first_seen": "2026-10-03T12:00:00Z",
        "last_seen": "2026-10-04T00:25:00Z"
      }
    }
  ]
}
FieldMeaning
meta.returnedNumber of events returned on this page.
meta.has_nextWhether more matching events are available now.
meta.next_cursorContinuation checkpoint. May still be present when has_next is false; may be null on an empty first page.
meta.quota_remainingRemaining token balance or cycle allowance after this query is charged.
items[].id / timestampEvent identifier for deduplication and feed event time in UTC. The time window filters this timestamp.
items[].actionupsert adds or updates the indicator in your local dataset. revoke withdraws it from the active feed; retain the event for your history.
items[].reasonReason code describing the change, when available.
items[].indicatorIndicator type and value, with file hashes or an IP version where applicable. host results use type domain or ip.
items[].analysis / validity / tagsClassification, observation and expiration times, and tags when available.
items[].context / stixAdditional context and STIX references in native full responses, when available.

Continue and resume

  1. Process the page, then save meta.next_cursor as your checkpoint when provided.
  2. While meta.has_next is true, request the next page using that cursor. Keep the same account, mode, format and types.
  3. When has_next is false, stop paging through a completed window. For an open-ended feed, pause before polling again with the latest cursor.
A cursor can still be returned when has_next is false. Its presence does not mean another page is available now. Empty queries are charged, so avoid continuous polling at the end of the feed.

The cursor retains your time window. To start a different window, omit the cursor and supply new time bounds. If an empty first page has no cursor, keep your original since when polling again. For a future until, pause at the current end and resume later within that same window.

cURL · next feed page
curl --get "https://api.virussign.com/v1/feed/events" \
  --header "x-api-key: $VIRUSSIGN_API_KEY" \
  --data-urlencode "mode=events" \
  --data-urlencode "types=file,host,url" \
  --data-urlencode "cursor=YOUR_NEXT_CURSOR" \
  --data-urlencode "limit=50"

This example processes a completed window with a ten-page budget. Replace the printed events with your own processing, and save each checkpoint only after its page has been successfully processed.

Python · process feed pages
import os
import requests  # pip install requests

endpoint = "https://api.virussign.com/v1/feed/events"
params = {
    "mode": "events",
    "types": "file,host,url",
    "since": "2026-10-04T00:00:00Z",
    "until": "2026-10-04T01:00:00Z",
    "limit": 50,
}
# To resume this window, set params["cursor"] to your saved checkpoint.
with requests.Session() as session:
    session.headers["x-api-key"] = os.environ["VIRUSSIGN_API_KEY"]
    for _ in range(10):  # Explicit page budget for evaluation.
        response = session.get(endpoint, params=params, timeout=(5, 60))
        response.raise_for_status()
        page = response.json()
        for event in page["items"]:
            print(event)  # Replace with durable, idempotent processing.
        meta = page["meta"]
        cursor = meta.get("next_cursor")
        if cursor:
            # Save only after all events on this page are processed.
            print("Checkpoint:", cursor)
        print("Remaining allowance:", meta.get("quota_remaining"))
        if not meta["has_next"]:
            break  # Finished this completed time window.
        if not cursor or cursor == params.get("cursor"):
            raise RuntimeError("Missing or repeated feed cursor")
        params["cursor"] = cursor
    else:
        print("Page budget reached; resume from the last checkpoint.")

Snapshot mode

Use mode=snapshot to retrieve current, unexpired upsert entries within the selected time window. Use mode=events for ongoing change tracking, including revocations. Start a new request without a cursor when switching modes.

STIX output

Set format=stix to receive a STIX 2.1 bundle. Read indicators from objects; revocations are represented by revoked=true. Pagination and remaining quota are supplied in response headers instead of a meta object. The media type is application/stix+json;version=2.1.

cURL · STIX feed and headers
curl --get --include "https://api.virussign.com/v1/feed/events" \
  --header "x-api-key: $VIRUSSIGN_API_KEY" \
  --data-urlencode "mode=events" \
  --data-urlencode "types=file,host,url" \
  --data-urlencode "since=2026-10-04T00:00:00Z" \
  --data-urlencode "until=2026-10-04T01:00:00Z" \
  --data-urlencode "limit=50" \
  --data-urlencode "format=stix"
Response headerMeaning
X-LimitRequested page size.
X-Has-More1 means more events are available now; 0 means the current end has been reached.
X-Next-CursorContinuation checkpoint, when available. Its presence alone does not mean another page is available.
X-Quota-RemainingRemaining token balance or cycle allowance after charging.
LinkContinuation URL with rel="next", when a cursor is available. Check X-Has-More before following it immediately.

Use X-Has-More to control paging, and save X-Next-Cursor after processing the bundle. Selecting STIX does not change the billing rule. See Quotas & usage for examples.

Quotas & usage

Your access plan defines permissions, balance or cycle allowance, and any expiration. IOC lookups and feed queries use different charging rules.

IOC and relationship queries

IOC lookups and standalone relationship pages each use one query unit. Usage is based on accepted queries, including lookups with no matching record. A request that fails or times out after processing begins may also use quota; each retry is a separate request.

OperationQuery units
IOC detail1 per accepted lookup, including any relationships included in that response.
Standalone relationship page1 per accepted page, regardless of the number of related items returned.
No matching IOC1 when the lookup is processed.

Feed queries

Feed queries are charged after the query completes, based on events returned: one query unit per started block of 50 events, with a minimum of one unit per request, including empty results. At the standard rate, one query unit costs one token. Your access plan defines any account-specific rate.

One token per 50 events, rounded up; minimum one token per request at the standard rate. Charges use the events actually returned on each page, not the requested limit. An empty page still costs one token.
Events returned on one pageTokens at the standard rate
01
1–501
51–1002
101–1503

For example, a request with limit=50 that returns 12 events costs one token. The minimum applies separately to every page and polling request. Charges are deducted after the feed query completes; delivery interruptions or client timeouts do not necessarily prevent a charge.

Remaining allowance

Token plans apply the token cost per query unit defined in your plan. Cycle plans deduct the same query units from the current cycle allowance. Native responses report the remaining amount in meta.quota_remaining; STIX feed responses use X-Quota-Remaining.

Rate limits and total quota are separate controls. Handle 429 even when your balance is positive. Use bounded retries and allow a pause between feed polls.

Errors & retries

JSON · quota error (HTTP 403)
{
  "detail": {
    "error": {"code": 7, "message": "quota exceeded"}
  }
}
HTTP statusMeaningAction
400Invalid parameters, cursor or limitCorrect the request before retrying.
401Missing or invalid API keyCheck the x-api-key header and issued credentials.
403Quota exceeded, account disabled or permission deniedInspect detail.error.code and message; request quota or permissions as needed.
404IOC or relationship not foundTreat as no available result, not a safe verdict. Check type and relation.
422Request validation errorInspect the validation details for incorrect parameter types or ranges.
429Rate limit exceededRespect Retry-After if present; otherwise use bounded exponential backoff with jitter.
500 / 502 / 503 / 504Server or upstream failureRetry conservatively; keep retry count bounded and remember a retry may consume quota.

Use explicit connection and read timeouts. A client timeout does not prove that the server cancelled a query or refunded its cost. Avoid unlimited retry loops.

Integration checklist & support

  • Verify the issued key’s endpoints, permissions, quota and expiration.
  • Start with a small, representative set of your own indicators.
  • Tolerate omitted optional fields and unknown additional fields.
  • Limit concurrency, handle errors and persist pagination checkpoints.
  • Measure coverage and usefulness separately from detection accuracy.
  • Confirm commercial integration, caching and redistribution rights before production use.

When reporting an issue, include the endpoint, UTC timestamp, HTTP status and meta.request_id if available. Never send your API key. Remove sensitive indicators unless they are necessary for troubleshooting.

API v1 · Contact us for integration help or to review your access plan.

Files are hashed locally in your browser and never uploaded. Only the SHA-256 is sent for lookup. Standard query quota applies.