Vulnerability Feed API Both¶
The Vulnerability Feed Broker is SentriKat's centralized, read-only feed of the public vulnerability landscape: the exploited-vulnerability catalogue, the primary CVE database (CVSS and applicability), exploit probability and the software identity catalogue, plus a beyond-the-primary-database exploit-intel layer. SentriKat operates it so that client installations (especially on-premises and air-gapped) pull this data from one place instead of each hammering the upstream feeds.
What it is, and isn't
The broker is a cache/mirror of authoritative public data, not a detection engine. It serves raw, upstream-faithful records. It does no matching against your inventory, so it produces no false positives of its own: confidence scoring and false-positive suppression happen later, in the SentriKat detection engine on the client side.
Coverage scope: the broker carries the primary CVE database, the exploited-vulnerability feed, exploit probability, the identity dictionary, exploit intel, distro advisories and package advisories, plus curated mappings. Vendor advisories (Red Hat CSAF and equivalents) are the one family that stays direct on the client, so an installation that relies on the broker still fetches those itself and coverage is never lost.
Base path: /api/v1/vuln-feed · Contract version: 0.3.0 (returned in the Contract-Version response header).
Authentication¶
Every endpoint except /health requires the same Bearer scheme as KB Sync:
Authorization: Bearer <first 64 chars of your signed license token>
X-Installation-ID: SK-INST-XXXXXXXX
The licence's edition maps to a feed tier, and each endpoint requires a minimum tier:
| Edition | Feed tier | Can call |
|---|---|---|
| Community / Demo | community | /manifest, /cpe-dictionary, /epss, /curated-mappings |
| Professional | professional | all of the above + /vulnerabilities, /cve/{id}, /exploit-intel, /distro-advisories, /osv-advisories, /bundle |
Requests are rate-limited per installation (by X-Installation-ID): 600 requests/hour on the bulk dataset endpoints, 300/hour on /cpe-dictionary and 120/hour on /manifest. Page size and rate limit are one constraint, not two: at the maximum page size of 500, 600 requests/hour is 300,000 rows/hour.
They are counted per installation, not per IP, so a NAT'd fleet sharing one egress IP isn't throttled as a single client.
Error envelope¶
All errors return a consistent body:
{
"error": "tier_insufficient",
"message": "this endpoint requires the professional tier or higher",
"documentation_url": "https://docs.sentrikat.com/api/vuln-feed"
}
Common codes: auth_invalid_signature, auth_installation_unknown, auth_installation_suspended, tier_insufficient, not_found, bad_request.
Endpoints¶
GET /health¶
Public (no auth). Liveness and per-dataset freshness so a monitor or status page can alert when ingestion silently stops.
{
"status": "ok",
"contract_version": "0.3.0",
"datasets": {
"vulnerabilities": { "count": 1342, "last_modified": "2026-06-26T05:15:00", "age_hours": 1.2, "stale": false },
"cpe_dictionary": { "count": 89234, "last_modified": "2026-06-22T04:30:00", "age_hours": 96.0, "stale": false },
"exploit_intel": { "count": 412, "last_modified": "2026-06-26T05:15:00", "age_hours": 1.2, "stale": false }
},
"checked_at": "2026-06-26T06:25:00+00:00"
}
status is ok, stale (a dataset is older than its budget, 48h for exploited/probability/exploit, 240h for the identity dictionary), or empty (returns HTTP 503).
GET /manifest¶
Tier: community. Dataset sizes + cursors so a client can plan incremental pulls, plus your tier and the coverage note.
GET /vulnerabilities¶
Tier: professional. Paginated CVE records (the exploited-vulnerability catalogue ∪ enriched).
| Query param | Default | Notes |
|---|---|---|
page | 1 | |
page_size | 100 | max 500 |
since | – | ISO 8601. Filters on ingested_at, when the broker took the row in, not on when upstream last changed it |
since_id | – | The other half of the cursor: the last id you saw at that same ingested_at |
The cursor is ingested_at, not last_modified
A row's last_modified is upstream's timestamp and does not advance in the order the broker receives rows, so seeding ?since= from it skips rows with no error anywhere: the pull succeeds, returns fewer rows, and nothing reports a problem. Always page on ingested_at, and pass since_id with it so rows sharing one timestamp are not cut in half at a page boundary.
Each dataset has its own cursor field. /health lists them under cursor_fields_by_dataset; read it there rather than assuming. The older cursor_field is still returned and describes this endpoint only, so a client that reads it for another dataset seeds the cursor from a column that does not exist there.
Incremental pulls: pass ?since=<the ingested_at you saw>&since_id=<its id> or the If-Modified-Since header. If nothing changed, the broker returns 304 Not Modified. Response is a pagination envelope:
{
"contract_version": "0.3.0",
"page": 1, "page_size": 100, "total": 1342,
"next_page": "/api/v1/vuln-feed/vulnerabilities?page=2&page_size=100",
"data": [ { "cve_id": "CVE-2024-3400", "severity": "CRITICAL", "is_actively_exploited": true, "epss_score": 0.97, "last_modified": "2026-06-26T05:15:00" } ]
}
GET /cve/{cve_id}¶
Tier: professional. A single CVE with its CPE applicability (cpe_data).
GET /cpe-dictionary¶
Tier: community. Paginated CPE dictionary (?since= / 304 supported).
GET /exploit-intel¶
Tier: professional. Paginated exploit signal per CVE, beyond what the primary database carries.
GET /epss¶
Tier: community. Paginated exploitation-probability scores per CVE.
Cursor: (epss_updated_at, id), and every row carries epss_updated_at. It is not ingested_at: a score changes without the broker taking the row in again, so a client paging on ingested_at saw each score once and never saw it move, on a dataset whose whole purpose is to move.
GET /distro-advisories¶
Tier: professional. Paginated Linux distribution security advisories (Ubuntu USN, Debian, Oracle ELSA, Amazon ALAS and the RHEL family), including the backport-aware fixed-version data that stops a patched package being reported as vulnerable.
GET /osv-advisories¶
Tier: professional. Paginated package advisories across the supported language ecosystems, matched on (ecosystem, name, version) rather than on a CPE guess.
GET /curated-mappings¶
Tier: community. Paginated vendor/product to CPE identities curated centrally from the whole fleet's contributions, so an identity resolved once is resolved for everyone.
Cursor: (curated_at, id), and both travel in the row. The timestamp is served as curated_at, which is the name to build the cursor from.
GET /bundle¶
Tier: professional. Downloads the latest signed offline bundle (.tar.gz) for air-gapped installs. See below.
Offline bundle (air-gapped)¶
A self-contained, signed snapshot for installations with no internet access.
- Archive:
.tar.gz(gzip). - Datasets: NDJSON (one JSON object per line), field names identical to the REST output, so the client uses one parser for both API and bundle. The
.jsonlmembers are plain text, not individually compressed: the archive's own gzip is the only compression layer. - Integrity: each dataset has a
sha256recorded inmanifest.json. - Authenticity: a detached
manifest.json.sigsigns the canonicalmanifest.jsonwith the same RSA keypair as licences (RSA-PKCS1v15-SHA256, base64). - Freshness: the signed manifest carries a strictly increasing
sequenceand anexpires_at, so an importer can refuse an older or an expired bundle.
Archive layout. Read the file names from manifest.json rather than assuming this list: datasets are added over time, and the dataset name is the contract.
manifest.json
manifest.json.sig
vulnerabilities.jsonl
cpe_dictionary.jsonl
cpe_data.jsonl
kev_history.jsonl
exploit_intel.jsonl
distro_advisories.jsonl
osv_advisories.jsonl
manifest.json:
{
"contract_version": "0.3.0",
"format_version": 1,
"sequence": 47,
"generated_at": "2026-06-26T05:45:00Z",
"expires_at": "2026-07-10T05:45:00Z",
"datasets": [
{"name": "vulnerabilities", "filename": "vulnerabilities.jsonl", "record_count": 1342, "sha256": "…"},
{"name": "cpe_data", "filename": "cpe_data.jsonl", "record_count": 5821, "sha256": "…"}
],
"signature": {"algorithm": "RSA-PKCS1v15-SHA256", "format": "detached", "file": "manifest.json.sig", "signed": "manifest.json"}
}
Verifying a bundle¶
Verify the signature over the raw manifest.json bytes (do not re-serialise), then the per-dataset sha256:
import tarfile, json, hashlib, base64
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
with tarfile.open("sentrikat-feed-bundle.tar.gz", "r:gz") as t:
manifest_bytes = t.extractfile("manifest.json").read() # raw bytes
sig = base64.b64decode(t.extractfile("manifest.json.sig").read())
licensing_public_key.verify(sig, manifest_bytes, # same key as licences
padding.PKCS1v15(), hashes.SHA256())
manifest = json.loads(manifest_bytes)
for d in manifest["datasets"]:
data = t.extractfile(d["filename"]).read()
assert hashlib.sha256(data).hexdigest() == d["sha256"]
rows = [json.loads(line) for line in data.splitlines()] # NDJSON
Then check freshness, which the signature alone cannot give you:
- reject a bundle whose
sequenceis lower than or equal to the last one you imported (an old bundle served again), - reject a bundle whose
expires_atis in the past (a bundle frozen in place).
sequence only moves when the data behind the bundle changes. A rebuild that finds nothing new keeps serving the bundle you already have, with the same number.
Data freshness & reliability¶
The feed is refreshed continuously from authoritative public sources. You don't manage any of that. What you can rely on as a client:
- Check freshness any time via
GET /health(per-datasetlast_modified,age_hours,stale). - Graceful behaviour during upstream outages: the broker keeps serving the last known-good data rather than going blank, so a temporary problem at an upstream shows up as staleness, not an empty response.
- Keep your own direct feeds as a safety net. The broker covers CVE records, exploited vulnerabilities, probability, identities and exploit intel only; your installation should continue to ingest vendor-advisory, distro and package advisory feeds directly. If the broker is unreachable, fall back to direct upstreams. Coverage is never lost.
Security¶
- Access is limited to licensed installations (Bearer + tier).
- The API is read-only and rate-limited per installation.
- Offline bundles are RSA-signed and per-dataset hashed, so a tampered bundle is rejected before import (fail-closed).
- The feed data itself is public vulnerability intelligence, the guarantees here are integrity and access control, not secrecy.