Ingestion.

Ten upstream connectors on independent cadences, each run recorded whether it succeeds or not.

A single run

SCHEDULERper-connectorsetIntervalfetch upstreamHTTPnormaliseto STIX shapeupsert+ evidenceRECORDED ON EVERY ATTEMPTingestion_runsstarted_at · status · countsokpartialfailedfreshness anywhere in the product is derived from the most recent run row — never from a stored "last updated" flaga failed run leaves the previous records in place; it does not blank the source
Fig. One connector run: cadence, fetch, normalise, upsert — plus the run row written on every attempt.

The connectors

ConnectorGives usEntity
abusech-urlhausMalware distribution URLsindicators
abusech-threatfoxIOCs with malware attributionindicators
abusech-malwarebazaarMalware samples and hashesindicators · malware
alienvault-otxCommunity pulsesindicators · actors
cisa-kevKnown exploited vulnerabilitiesvulnerabilities
epssExploit prediction scoresvulnerabilities
github-ghsaAdvisories for packagesvulnerabilities
nvd-cveThe CVE corpus with CVSSvulnerabilities
mitre-attackTechniques, tactics, groupsattack_patterns · actors
ransomware-liveRansomware victim postingsactors · indicators

Cadence

Each connector has its own interval, because the upstreams move at different speeds — KEV changes a few times a week, URLhaus continuously. Intervals live in the ingest-scheduler worker as in-process timers.

No external scheduler

The host platform has no native cron, so this is setInterval inside a long-running worker rather than a scheduled job. If that worker is not running, nothing ingests — and the failure is silent staleness rather than an error anyone gets paged for.

That is exactly why freshness is derived from ingestion_runs and surfaced on the public status page: it makes a stopped scheduler visible instead of invisible.

What a run records

The run row is opened before the fetch, so a process that dies mid-request still leaves evidence that the attempt happened.

  • started_at — when the attempt began.
  • A status: succeeded, partial, or failed.
  • Counts of records read and written.
  • The error, when there is one, truncated to a sane length.

A failed run is non-destructive: previous records stay in place. A source that fails for a week looks stale, not empty — which is the honest representation of what happened.

Your own intel

Beyond the upstream connectors, a tenant can push its own indicators over POST /v1/private/submit. Those are written tenant-scoped and never merged into the global corpus.

  • Up to 500 indicators per call.
  • Per-indicator verdict, confidence, note, tlp and tags.
  • A default_tlp for the batch, so markings do not have to be repeated.
Ingestion — OmniIntel docs · OmniIntel