ThreatPulse CTI API
Read-only cyber threat intelligence: prioritised threat records, vulnerabilities, indicators, the attribution register, and publication history. Version 1.0.
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.
- Read-only. Only
GET(andOPTIONSfor pre-flight) is accepted. Anything else returns405with anAllowheader. - CORS.
Access-Control-Allow-Origin: *on the read endpoints. - Caching.
Cache-Control: public, max-age=60. - Attribution. The attribution register lists every declared source with its licence and terms. Reuse of the data is subject to those upstream terms, not to a ThreatPulse licence.
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"] }
| Status | Meaning |
|---|---|
400 | A parameter is invalid. message says which; validation errors may add allowed_values. |
403 | Reserved for the write endpoint: the calling address is not on its allowlist. The read endpoints above never return it. |
404 | No record for the requested identifier. |
405 | Wrong HTTP method; see the Allow header. |
500 | Server-side failure. The body reports error_class and error_message. |
503 | No 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.
| Parameter | Type | Notes |
|---|---|---|
limit | int | 1–100, default 50. |
offset | int | Default 0. |
severity | enum | critical, high, medium, low, informational. |
exploitation_status | enum | confirmed, suspected, unknown, not_exploited. |
cve | string | CVE-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
| Parameter | Required | Notes |
|---|---|---|
uuid | yes | The 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.
| Parameter | Type | Notes |
|---|---|---|
limit | int | 1–100, default 50. |
offset | int | Default 0. |
cve | string | Exact CVE identifier, uppercased. |
severity | enum | critical, high, medium, low, informational. |
kev_listed | bool | true 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.
| Parameter | Type | Notes |
|---|---|---|
limit | int | 1–100, default 50. |
offset | int | Default 0. |
indicator_type | string | For example ip, domain, url, md5, sha256. |
value | string | Exact normalised value, lowercased. |
active | bool | true, 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.
| Parameter | Type | Notes |
|---|---|---|
limit | int | 1–100, default 50. |
offset | int | Default 0. |
source_key | string | Exact source key, for example cisa_kev. |
source_type | string | Filter 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.
| Parameter | Type | Notes |
|---|---|---|
limit | int | 1–100, default 50. |
offset | int | Default 0. |
source_key | string | Exact source key. |
tier | int | Restrict to one source tier. |
ingested | bool | true 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.
| Parameter | Type | Notes |
|---|---|---|
limit | int | 1–100, default 50. |
offset | int | Default 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.