# API

Vulnerability-Lookup exposes a REST API under the `/api` path of every
instance. The same instance also serves an **interactive Swagger UI at
`/api/`**, where you can browse the endpoints and try them out live.
[PyVulnerabilityLookup](https://github.com/vulnerability-lookup/PyVulnerabilityLookup)
is the official Python client for the API.

```{note}
Building a mirror, scanner, or other automated consumer? Read
[Access patterns for automated consumers](access-patterns.md) first — it
covers the canonical sync path (`since=` + the pub/sub stream),
identification expectations, and the rate-limit posture. The same
guidance is exposed in machine-readable form at
`/.well-known/api-policy.json`.
```

## Quick start

The examples below use the public CIRCL instance
(`https://vulnerability.circl.lu`); replace the host with your own. Responses
are JSON.

Look up a vulnerability by ID:

```bash
$ curl 'https://vulnerability.circl.lu/api/vulnerability/CVE-2024-38063'
```

Filter comments by metadata — for example those flagged as a Proof of Concept.
The `meta` parameter is a JSON value matched against the comment's `meta` field
(JSONB containment). Use `-G`/`--data-urlencode` so curl URL-encodes the JSON
for you instead of escaping it by hand:

```bash
$ curl -G 'https://vulnerability.circl.lu/api/comment/' \
    --data-urlencode 'meta=[{"tags":["vulnerability:information=PoC"]}]'
```

Tags are matched exactly, so use the full machine-tag value (for example
`vulnerability:information=PoC`, not just `PoC`).

Paginate any list endpoint with `page` and `per_page` (see
[Response format](#response-format) for the envelope):

```bash
$ curl 'https://vulnerability.circl.lu/api/sighting/?vuln_id=CVE-2024-38063&per_page=50'
```

For the full list of endpoints, parameters, and response models, see the
[OpenAPI specification](#openapi-specification) below.

## Browsing VEX records

Vulnerability-Lookup stores vendor VEX statements as enrichment metadata on
existing vulnerability records. In the web UI, open **VEX** from the top menu
to browse available VEX records and pivot to the related vulnerability. API
clients can use the VEX browse endpoint:

```bash
$ curl 'https://vulnerability.circl.lu/api/vex/?per_page=20'
```

The response is paginated and each record includes `vulnerability_id` and
`vulnerability_url` (instance-relative, resolve it against the host you
queried), so clients can pivot from the VEX record to the related
CVE/vulnerability page. You can restrict the browse view to one metadata source
(`redhat_vex` or `microsoft_vex`; unknown sources are rejected with a 400) or
filter with `q`, a case-insensitive substring match on the vulnerability id,
document title, or source name:

```bash
$ curl 'https://vulnerability.circl.lu/api/vex/?source=redhat_vex&q=CVE-2024'
```

Use `/api/vex/{vuln_id}` to retrieve the VEX statements attached to one
vulnerability, and `/api/vex/{vuln_id}/{source}/csaf` to retrieve the stored
CSAF document for that source.

## Local exploit hazard

The `POST /api/exploit-hazard` endpoint computes a user-parameterized local exploit hazard for one vulnerability instance. It accepts a `vulnerability_id` to load local EPSS/CVSS metadata when available, or an explicit ELM probability such as EPSS (`epss`), plus optional control effectiveness values (`controls` or `controls_by_attack_vector`), optional KEV policy parameters (`kev`, `kev_weight`, `kev_floor`), and either an `exponential` or `weibull` hazard model. When `model` is `weibull`, provide `published` or `age_days`; the default shape parameter is `0.605`, as calibrated in the referenced [local exploit hazard paper](https://arxiv.org/abs/2607.24618).

```bash
$ curl -X POST 'https://vulnerability.circl.lu/api/exploit-hazard' \
    -H 'Content-Type: application/json' \
    -d '{
      "epss": 0.2,
      "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "controls_by_attack_vector": {"network": [0.5]},
      "model": "weibull",
      "published": "2026-07-01",
      "horizon_days": 7
    }'
```

The response includes the residual `adjusted_likelihood`, an instantaneous `daily_hazard`, and the requested-window `horizon_probability`.

Notes on the parameters:

- The KEV policy follows section 3.3 of the paper: `kev_weight` and `kev_floor` modify the ELM likelihood *before* control effectiveness is applied, and only when `kev` is true. `kev_floor` is therefore not a floor on the final result — stacked controls can still reduce the outcome below it. The paper includes this mechanism as a compliance affordance (a KEV listing is an observed exploitation, not a higher forecast), not as a statistical correction.
- `weibull_shape` accepts values between `0.1` and `5.0`; the paper's calibration is `0.605` with a recommended sensitivity range of 0.5–0.7.
- `age_days`, `horizon_days`, and `elm_horizon_days` are bounded at 36600 (about 100 years); `elm_horizon_days` must be at least 1. `controls` arrays accept at most 256 values.

Invalid parameters return HTTP 400 with a descriptive message; HTTP 503 is returned when the storage backend cannot be reached to resolve a `vulnerability_id`.

`POST /api/exploit-hazard/batch` aggregates a whole set of instances at once (section 3.6 of the paper): the response carries each instance's result in order, the aggregate daily hazard (the sum of the per-instance hazards, H_agg), the expected number of exploitation events over the horizon, and the Poisson probability of at least one event. `instances` accepts the same objects as the single endpoint — or plain vulnerability-id strings as shorthand for `{"vulnerability_id": ...}` — up to 256 per request, and one `horizon_days` is forced on every instance so the per-item probabilities are comparable. An optional `parameters` object applies the same per-subscription parameter set the notification form accepts (`controls`, `controls_by_attack_vector`, `kev_weight`, `kev_floor`, `weibull_shape`) to every instance, with an instance's own values winning on overlap. An instance that fails validation is reported in its result slot as `{"error": ...}` and excluded from the aggregate instead of failing the batch; slots resolved from a `vulnerability_id` echo it back.

```bash
$ curl -X POST 'https://vulnerability.circl.lu/api/exploit-hazard/batch' \
    -H 'Content-Type: application/json' \
    -d '{
      "instances": ["CVE-2025-53770", {"epss": 0.2, "model": "weibull", "age_days": 40}],
      "parameters": {"controls_by_attack_vector": {"network": [0.5]}},
      "horizon_days": 7
    }'
```

The paper's caveat applies to the aggregate: correlated vulnerabilities (for example many in the same product) make it an upper-bound trend indicator rather than a calibrated event count.

The same model enriches vulnerability notifications: email and webhook reports order vulnerabilities by their individual hazard, annotate each with its EPSS score, age, and horizon exploitation probability, and carry the batch aggregate (expected exploitation events per day and the probability of at least one event, per section 3.6 of the paper). Webhook payload templates can reference the aggregate through the `{hazard_events_per_day}` and `{hazard_probability}` placeholders. Each notification subscription can localize its estimates with stored hazard parameters (control effectiveness values, per-attack-vector controls, KEV policy, Weibull shape) configured on the notification form; vulnerabilities present in the local KEV catalog activate the subscription's KEV policy automatically. Without stored parameters, reports carry the global (uncontrolled) estimate.

Beyond the per-report delta, the notification daemon evaluates each subscription's **standing exposure** once a day: the aggregate hazard of the entire current vulnerability set the subscription watches (the paper's exposed-set aggregate H_agg, section 3.6), localized with the same stored parameters. Reports include the standing figure, and a subscriber can set an **exposure alert threshold** (expected exploitation events per day) on the notification form: when the daily evaluation crosses it upward, an alert is sent on the subscription's delivery channels, listing the top hazard contributors — the removals that lower the exposure most, mirroring the paper's remediation ranking (section 3.7). The alert then stays silent until the exposure falls back below 90% of the threshold. The evaluation is engineered to be cheap at scale: EPSS scores are read fresh from storage in pipelined batches, record-derived inputs (CVSS vector, publication date) are cached for a week with a bounded refill per run, and subscriptions watching the same vendor/product with the same parameters share one computation.

## Response format

All list endpoints return a paginated response with the following structure:

```json
{
    "metadata": {
        "count": 42,
        "page": 1,
        "per_page": 10
    },
    "data": []
}
```

(OpenAPI)=

## OpenAPI specification

The complete reference below is generated from the API's OpenAPI (Swagger)
specification — the same spec that powers the interactive Swagger UI at
`/api/`.

```{eval-rst}
.. openapi:: _static/files/swagger.sanitized.json
```
