# EPSS Exploit Prediction Scores Scraper - FIRST.org (`ninhothedev/epss-scores-scraper`) Actor

$1/1K 🔥 Scrape EPSS exploit prediction scores from FIRST.org: the daily-updated probability that a CVE will be exploited in the next 30 days. Lookup by CVE list, pull the highest-risk CVEs, stream the full 350k-row daily dump, or get 30-day score history with trends. No API key.

- **URL**: https://apify.com/ninhothedev/epss-scores-scraper.md
- **Developed by:** [ninhothedev](https://apify.com/ninhothedev) (community)
- **Categories:** Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## EPSS Exploit Prediction Scores Scraper

Scrape **EPSS (Exploit Prediction Scoring System)** data from **FIRST.org** — the daily-updated probability that a given CVE will be **exploited in the wild within the next 30 days**. That is the prioritisation signal severity scores lack: CVSS tells you how bad a vulnerability *would* be if exploited, EPSS tells you how *likely* it is to actually be exploited. No API key, no login, no proxy required.

Covers **354,000+ CVEs**, rescored every single day by FIRST.org's machine-learning model.

### Why EPSS instead of just CVSS?

**CVSS** is a static severity rating — it describes the theoretical impact and exploitability characteristics of a flaw, and it barely changes over a CVE's lifetime. **EPSS** is a live, empirical probability derived from real-world exploitation telemetry, so it re-ranks your backlog every day and tells you which of your 4,000 "critical" CVSS 9.8s are the ~2% that attackers are actually weaponising right now.

### Features

- **4 scrape modes** — explicit CVE lookup, top-risk listing, full daily dump, and 30-day score history.
- **No API key** — FIRST.org's EPSS API is free and public.
- **Never silently drops a CVE** — a CVE the EPSS model does not know still returns a row with `found: false` and null scores, so your joins stay intact.
- **Derived risk fields** — `risk_band`, `in_top_1pct`, `in_top_10pct`, percentage-formatted scores.
- **Trend detection** in history mode — `previous_epss`, `epss_change`, and `rising` / `falling` / `stable`.
- **Streaming bulk mode** — the ~350k-row gzipped daily dump is decompressed incrementally, so memory stays flat.
- Numbers arrive from the API as strings (`"0.97236"`) and are converted to real floats for you.

### Modes

| Mode | What it does | Uses |
|---|---|---|
| `top` *(default)* | Highest-risk CVEs above the `minEpss` probability threshold, sorted highest first | `minEpss`, `maxItems` |
| `cves` | Look up an explicit list of CVE ids | `cves` |
| `bulk` | Stream the full daily EPSS dump (354k+ CVEs) | `maxItems` |
| `history` | 30-day EPSS time series per CVE, with trend | `cves`, `days`, `maxItems` |

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | string | `top` | `top`, `cves`, `bulk` or `history`. |
| `cves` | array | `[]` | CVE ids for `cves` / `history` mode, e.g. `CVE-2021-44228`. |
| `minEpss` | number | `0.5` | Exploit-probability threshold for `top` mode. `0.5` = 50% chance of exploitation in 30 days. |
| `days` | integer | `30` | Days of score history per CVE in `history` mode (API window is 30). |
| `maxItems` | integer | `500` | Hard cap on dataset rows (max 50,000). |

```json
{
  "mode": "cves",
  "cves": ["CVE-2021-44228", "CVE-2014-0160"],
  "maxItems": 500
}
```

### Output

One row per CVE per scoring date.

```json
{
  "cve": "CVE-2021-44228",
  "epss": 0.99999,
  "epss_percent": 99.999,
  "percentile": 1.0,
  "percentile_pct": 100.0,
  "date": "2026-07-29",
  "risk_band": "critical",
  "in_top_1pct": true,
  "in_top_10pct": true,
  "found": true,
  "previous_epss": null,
  "epss_change": null,
  "trend": null,
  "url": "https://www.first.org/epss/",
  "source": "first-epss",
  "scraped_at": "2026-07-30T13:40:11Z"
}
```

| Field | Description |
|---|---|
| `cve` | CVE identifier, upper-cased. |
| `epss` | Exploit probability, float `0`–`1`. |
| `epss_percent` | `epss * 100`, rounded to 3 decimals. |
| `percentile` | Rank against all scored CVEs, float `0`–`1`. |
| `percentile_pct` | `percentile * 100`, rounded to 2 decimals. |
| `date` | Scoring date (ISO `YYYY-MM-DD`). |
| `risk_band` | `critical` (≥0.5), `high` (≥0.1), `medium` (≥0.01), else `low`. |
| `in_top_1pct` / `in_top_10pct` | Percentile ≥ 0.99 / ≥ 0.90. |
| `found` | `false` for CVEs EPSS does not score. |
| `previous_epss`, `epss_change`, `trend` | History mode: prior day's score, delta, and `rising` / `falling` / `stable`. |
| `url`, `source`, `scraped_at` | Provenance. |

All fields are nullable.

### Use cases

- **Vulnerability prioritisation** — patch the 2% of CVEs that are actually being exploited instead of every CVSS 9.8.
- **Risk scoring** — blend EPSS probability with your own asset criticality for a true expected-loss ranking.
- **SOC dashboards** — feed daily `bulk` runs into Grafana/Splunk and alert when a CVE in your stack crosses `risk_band: critical`.
- **Patch planning** — use `history` mode to catch CVEs whose exploit probability is *rising* and pull the patch window forward.
- **Compliance evidence** — document why a low-EPSS "critical" was deferred.

### Pricing

Roughly **$0.5 per 1,000 results**. A `bulk` run pulling all 354k CVEs takes a couple of minutes.

### Related actors

- [CIRCL CVE Scraper](https://apify.com/ninhothedev/circl-cve-scraper) — full CVE records, CVSS vectors and references.
- [GitHub Advisories Scraper](https://apify.com/ninhothedev/github-advisories-scraper) — package-level advisories and affected version ranges.
- [Exploit-DB Scraper](https://apify.com/ninhothedev/exploit-db-scraper) — public exploit code and proof-of-concepts.
- [NIST CPE Scraper](https://apify.com/ninhothedev/nist-cpe-scraper) — the CPE product dictionary for asset matching.

Pair this actor with CIRCL for the vulnerability detail and Exploit-DB for the weaponisation evidence — EPSS supplies the probability that ties them together.

### Notes

- Data © FIRST.org, published under the EPSS terms of use. Scores update daily.
- The FIRST.org API caps `limit` at **10,000** records per request; the actor pages automatically.
- Bulk mode reads `https://epss.cyentia.com/epss_scores-current.csv.gz` (redirects to Empirical Security's CDN).

### Development

```bash
python tests/test_mapping.py   # offline smoke tests, stdlib only
```

# Actor input Schema

## `mode` (type: `string`):

What to scrape. 'top' returns the highest-risk CVEs above the EPSS probability threshold. 'cves' looks up an explicit list of CVE ids. 'bulk' streams the full daily EPSS dump (~350,000 CVEs). 'history' returns the 30-day EPSS time series with trend for the CVEs you list.

## `cves` (type: `array`):

CVE identifiers to look up, e.g. CVE-2021-44228. Used by the 'cves' and 'history' modes and ignored by 'top' and 'bulk'. A CVE that EPSS does not know is still returned, with found=false and null scores.

## `minEpss` (type: `number`):

The exploit probability threshold for 'top' mode: only CVEs with an EPSS score greater than this value are returned, sorted highest first. 0.5 means a 50% chance of exploitation in the next 30 days. Lower it to 0.1 for a wider high-risk list.

## `days` (type: `integer`):

How many days of EPSS score history to return per CVE in 'history' mode. The FIRST.org time-series window is 30 days, so values above 31 are clamped. Ignored by the other modes.

## `maxItems` (type: `integer`):

Hard cap on the number of dataset rows produced by the run. One row is one CVE on one scoring date. Use a small value to sample before a full 'bulk' run.

## Actor input object example

```json
{
  "mode": "top",
  "cves": [
    "CVE-2021-44228",
    "CVE-2014-0160"
  ],
  "minEpss": 0.5,
  "days": 30,
  "maxItems": 500
}
```

# Actor output Schema

## `scores` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "cves": [
        "CVE-2021-44228",
        "CVE-2014-0160"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ninhothedev/epss-scores-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "cves": [
        "CVE-2021-44228",
        "CVE-2014-0160",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("ninhothedev/epss-scores-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "cves": [
    "CVE-2021-44228",
    "CVE-2014-0160"
  ]
}' |
apify call ninhothedev/epss-scores-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ninhothedev/epss-scores-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/3sJfIzRXkE3kVDdZf/builds/spwpxfkihUJ9BLK5J/openapi.json
