ThreatPulse CTI API

Read-only cyber threat intelligence: prioritised threat records, vulnerabilities, indicators, the attribution register, and publication history. Version 1.0.

Base URL
https://threatpulse.iliadis.net/api/cti/v1/

Every endpoint below is relative to it. Discovery lives at cti/v1/.

Access

The read endpoints are open: no key, no token, no allowlist. They are called directly from the browser, so they have to be. Nothing about the public contract needs authentication.

The single exception is publish.php, the write endpoint the ThreatPulse pipeline uses. It is limited to a fixed set of source addresses and requires a bearer token. It is not part of this contract and is documented in the deployment notes instead.

Response envelope

Every list endpoint returns the same shape:

{
  "status": "ok",
  "api_version": "1.0",
  "data": { "<collection>": [ ... ] },
  "pagination": {
    "limit": 50,
    "offset": 0,
    "returned": 50,
    "total": 1917,
    "has_more": true
  },
  "timestamp": "2026-11-01T17:46:10+00:00"
}

data is keyed by collection name — threats, vulnerabilities, indicators, sources, attribution, publications — and the key matches the endpoint.

Pagination

Every list endpoint accepts limit and offset. limit defaults to 50 and is clamped to 1–100; offset defaults to 0 and cannot be negative. Out-of-range values are clamped rather than rejected. Page with offset while has_more is true, or use pagination.total.

Errors

Failures return the HTTP status and a JSON body of this shape:

{ "status": "error", "message": "Invalid severity.",
  "allowed_values": ["critical", "high", "medium", "low", "informational"] }
StatusMeaning
400A parameter is invalid. message says which; validation errors may add allowed_values.
403Reserved for the write endpoint: the calling address is not on its allowlist. The read endpoints above never return it.
404No record for the requested identifier.
405Wrong HTTP method; see the Allow header.
500Server-side failure. The body reports error_class and error_message.
503No publication is available yet.

Endpoints

GET cti/v1/ — discovery

Machine-readable index: the endpoint list, API capabilities and a pointer to the attribution register. Intended as the entry point for clients that should not hard-code what is available.

curl -s https://threatpulse.iliadis.net/api/cti/v1/
{
  "status": "ok",
  "service": "ThreatPulse CTI API",
  "api_version": "1.0",
  "documentation": { "threats": "/api/cti/v1/threats.php", ... },
  "capabilities": { "read_only": true, "pagination": true, "cve_lookup": true, ... },
  "data_provenance": { "attribution_endpoint": "/api/cti/v1/attribution.php" }
}

GET cti/v1/status.php — liveness

Cheap health check. No database access, no parameters.

curl -s https://threatpulse.iliadis.net/api/cti/v1/status.php
{ "status": "ok", "service": "iliadis-cti-api", "version": "0.1.0" }

GET cti/v1/threats.php — prioritised threats

The main surface: correlated threat records with severity, priority and confidence, the vulnerabilities behind them, and the sources that reported them.

ParameterTypeNotes
limitint1–100, default 50.
offsetintDefault 0.
severityenumcritical, high, medium, low, informational.
exploitation_statusenumconfirmed, suspected, unknown, not_exploited.
cvestringCVE-YYYY-NNNN…, normalised to uppercase. Resolves the CVE to the threats it is associated with.
# Critical threats, newest first page
curl -s "https://threatpulse.iliadis.net/api/cti/v1/threats.php?severity=critical&limit=25"

# Which threats is this CVE behind?
curl -s "https://threatpulse.iliadis.net/api/cti/v1/threats.php?cve=CVE-2026-109619"
{
  "status": "ok",
  "api_version": "1.0",
  "data": { "threats": [ { "threat_uuid": "...", "severity": "critical", ... } ] },
  "pagination": { "limit": 25, "offset": 0, "returned": 25, "total": 26, "has_more": true },
  "timestamp": "2026-11-01T17:46:10+00:00"
}

An invalid cve returns 400 rather than an empty list, so a typo is distinguishable from a CVE with no threats.

GET cti/v1/threat.php — one threat

ParameterRequiredNotes
uuidyesThe threat's threat_uuid.
curl -s "https://threatpulse.iliadis.net/api/cti/v1/threat.php?uuid=THREAT_UUID"

Returns 404 for an unknown identifier and 400 when uuid is missing.

GET cti/v1/vulnerabilities.php — CVE records

Vulnerability records with CVSS, severity, KEV status and the threats each one is associated with.

ParameterTypeNotes
limitint1–100, default 50.
offsetintDefault 0.
cvestringExact CVE identifier, uppercased.
severityenumcritical, high, medium, low, informational.
kev_listedbooltrue or false — restrict to CISA KEV entries, or to everything else.
curl -s "https://threatpulse.iliadis.net/api/cti/v1/vulnerabilities.php?kev_listed=true&limit=10"

GET cti/v1/indicators.php — indicators of compromise

Published indicators — addresses, domains, URLs and hashes — with the malware family they were reported under and their current active state.

ParameterTypeNotes
limitint1–100, default 50.
offsetintDefault 0.
indicator_typestringFor example ip, domain, url, md5, sha256.
valuestringExact normalised value, lowercased.
activebooltrue, false, 1 or 0.
curl -s "https://threatpulse.iliadis.net/api/cti/v1/indicators.php?indicator_type=ip&active=true&limit=10"

GET cti/v1/sources.php — source health

Every declared source with its type, reliability and how many records it currently contributes — including sources that are declared but contributing nothing, which is the point of the endpoint.

ParameterTypeNotes
limitint1–100, default 50.
offsetintDefault 0.
source_keystringExact source key, for example cisa_kev.
source_typestringFilter by source type, lowercased.
curl -s "https://threatpulse.iliadis.net/api/cti/v1/sources.php?limit=100"

GET cti/v1/attribution.php — the attribution register

The authoritative statement of where the data comes from: each source's provider, licence, licence and terms URLs, TLP default, tier, and whether it is currently ingested. Any reuse must respect the upstream terms listed here.

ParameterTypeNotes
limitint1–100, default 50.
offsetintDefault 0.
source_keystringExact source key.
tierintRestrict to one source tier.
ingestedbooltrue or false — currently contributing, versus declared only.
curl -s "https://threatpulse.iliadis.net/api/cti/v1/attribution.php?ingested=true&limit=100"

GET cti/v1/publications.php — publication history

The publication ledger: one entry per pipeline run that reached the API, with its identifier, timestamps and record counts. Use it to tell a stale dataset from a live one.

ParameterTypeNotes
limitint1–100, default 50.
offsetintDefault 0.
curl -s "https://threatpulse.iliadis.net/api/cti/v1/publications.php?limit=5"

Response headers

Read endpoints send X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: no-referrer and Permissions-Policy: camera=(), microphone=(), geolocation=(). The JSON content type is application/json; charset=utf-8.

Not part of this contract

publish.php is the write endpoint used by the ThreatPulse pipeline. It requires a bearer token, is not for public consumption, and is documented in the deployment notes rather than here.