TRIX API reference
Vulnerability and threat intelligence over HTTPS: every CVE with its TRIS score, KEV, EPSS, exploit signals, the threat actors with documented use, and exploit prerequisites. JSON in, JSON out.
Get started in three steps
- Create a free account with Google, GitHub, or your email. Email sign-ups confirm the address first.
- On the account page, accept the TRIX API terms and issue a key. It starts with
cvk_and is shown once, so copy it then. - Make your first call:
curl -H "Authorization: Bearer $TRIX_KEY" \ https://trix.cveasyai.com/v1/cve/CVE-2021-44228
The base URL for every route is https://trix.cveasyai.com. GET https://trix.cveasyai.com/v1 needs no key and lists the routes and tiers, so a script can check what exists before it authenticates.
Authentication
Send your key as a bearer token on every request:
Authorization: Bearer cvk_...
We store only a hash of the key, so we cannot show it to you again. If you lose it, rotate it on the account page: the old key stops working the moment the new one is issued. Keep keys out of source control and browser code; call the API from a server or a script. Pro and above hold several keys at once, so each service can have its own and you can rotate or revoke one without touching the rest.
A request with no key, a revoked key, or an expired key gets 401.
Tiers and limits
| Community | Developer | Pro | Scale | Enterprise | |
|---|---|---|---|---|---|
| Price a month | Free | $49 | $495, or included in CVEasy AI | $1,490 | From $2,500, annual contract |
| Billed yearly | $490 | $4,950 | $14,900 | Annual | |
| Calls per minute | 60 | 120 | 600 | 1,500 | 3,000 |
| Calls per day (UTC) | 1,000 | 10,000 | 100,000 | 500,000 | 2,000,000 |
| List records per day | 5,000 | 50,000 | 1,000,000 | 5,000,000 | Unlimited |
| Largest list page | 50 | 200 | 1,000 | 1,000 | 1,000 |
| Lookups a day on the website | 300 | 2,000 | 5,000 | 10,000 | 10,000 |
| Active keys per account | 1 | 1 | 3 | 10 | 25 |
| CVE records, TRIS baseline, KEV, EPSS, delta sync | Yes | Yes | Yes | Yes | Yes |
| Actors with documented use, chain facts | Yes | Yes | Yes | Yes | Yes |
| Heuristic actor matches | No | Yes | Yes | Yes | Yes |
| Chain resolution and validated chains | No | Yes | Yes | Yes | Yes |
| Contextual TRIS against your asset context | No | No | Yes | Yes | Yes |
| Bulk snapshots | No | No | Yes | Yes | Yes |
| Score history per CVE | No | No | No | Yes | Yes |
| ACT alert webhooks | No | No | No | Yes | Yes |
| BASzy offensive intel | No | No | No | No | Per contract |
| License | Personal, research, your own organization | Personal, research, your own organization | Commercial: products and client services | Commercial, plus showing TRIX data to your own customers | Custom, including redistribution |
| Support | Docs | Email, one business day | Priority support | Named contact, SLA by contract |
Every response carries x-ratelimit-limit-day and x-ratelimit-remaining-day. Over a limit you get 429 with Retry-After and a body that says which window you hit (minute or day). Below Enterprise it also names next_tier, the tier that lifts the limit, and an upgrade_url. Days reset at 00:00 UTC.
List records count the CVE rows that GET /v1/cve list pages return; point lookups do not count against them. They are what stop one key copying the whole catalog in an afternoon. Upgrade from the account page: your existing keys move to the new limits within a minute of payment, with no change to your code. Moving between paid tiers works the same way, up or down.
CVEs
GET /v1/cve/{id}Community
One CVE, enriched. Fields include tris_baseline (0 to 100) and tris_band (ACT, ATTEND, TRACK, MONITOR), a one line summary, products (up to 10, primary first, with products_total), CVSS score and vector, cwe_ids, epss_score and epss_percentile, is_kev with its dates, has_public_poc, and coverage when we have written about it.
{
"id": "CVE-2021-44228",
"tris_baseline": 95,
"tris_band": "ACT",
"summary": "Apache Log4j2 2.0-beta9 through 2.15.0 JNDI features ...",
"products": ["Apache Log4j", "Siemens Capital", "..."],
"products_total": 20,
"severity": "CRITICAL",
"cvss_score": 10,
"cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H",
"epss_score": 0.99999,
"epss_percentile": 1,
"is_kev": 1,
"kev_date_added": "2021-12-10",
"has_public_poc": 1
}
Unknown CVE: 404. New CVEs appear after NVD publishes them.
GET /v1/cveCommunity
A filtered list. Query parameters, all optional:
| Parameter | Meaning |
|---|---|
kev=1 | Only CVEs on the CISA KEV list |
min_epss=0.5 | EPSS score at or above this. A number from 0 to 1. |
since=2026-09-01 | Changed on or after this date. This is how you keep a local copy in sync. Community reaches back 30 days, Developer 90, Pro and above any date. |
limit=50 | Page size: up to 50 on Community, 200 on Developer, 1,000 on Pro and above. |
cursor=... | Where to resume. Pass next_cursor from the previous page back unchanged. |
{ "count": 50, "next_cursor": "...", "results": [ { "id": "...", ... } ] }
Results are ordered by CVE ID, or by the field you filter on when you send since (change date) or min_epss (EPSS score). Treat next_cursor as an opaque string: do not build or edit it. It is null on the last page. Every row a list page returns counts against your list records for the day.
Context and scoring
GET /v1/context/{cve}Community
Everything about one CVE in one call, grouped as threat (description, products, references), risk (CVSS, TRIS baseline and band, EPSS, KEV, ransomware and wormable signals, and a plain English reasoning), exploits (public exploit, prerequisites: what an attacker needs and what they gain) and intel (actors with documented use).
POST /v1/tris/scoreCommunity for baselinePro with context
Score up to 100 CVEs in one call. Without a context, every tier gets the baseline. With a context, Pro keys get tris_contextual: the same CVE scored for your asset.
curl -X POST https://trix.cveasyai.com/v1/tris/score \
-H "Authorization: Bearer $TRIX_KEY" \
-H "Content-Type: application/json" \
-d '{
"cve_ids": ["CVE-2021-44228", "CVE-2024-3094"],
"context": {
"asset_criticality": "critical",
"internet_facing": true,
"business_impact": "critical",
"data_classification": "restricted",
"compensating_controls": false,
"reachable": true
}
}'
Every context field is optional, and each one you send adds a measured layer; the response lists them in measured_layers. Context is used for the request and never stored. A Community key that sends a context gets 402; drop the context for the baseline.
POST /v1/context/{cve}Pro
The fused context from the GET route, with tris_contextual filled in for the context you post. Same body shape as above, without cve_ids.
Threat actors
GET /v1/actor/cve/{id}Community
Actors seen using the CVE. By default only documented use counts: a named campaign, a vendor or government advisory, or MITRE ATT&CK. Each match has match_type, confidence, evidence and a ref_url when there is one.
{
"cve_id": "CVE-2021-44228",
"matches": [
{ "actor_id": "apt41", "actor_name": "APT41", "match_type": "direct_cve",
"confidence": 0.95, "source": "mitre-attack,cveasy-curated",
"evidence": "MITRE ATT&CK campaign C0017: ...",
"ref_url": "https://www.mandiant.com/resources/apt41-us-state-governments" }
],
"grade": "evidence only"
}
Add ?include_heuristic=1 on a Developer key or above for product and technique matches, always labelled with their match_type so they are never mistaken for evidence.
GET /v1/actor/resolve?name={alias}Community
Resolve any name or alias (APT29, Cozy Bear, Midnight Blizzard) to the canonical actor. ambiguous: true means two sources disagree, and both are returned.
GET /v1/actor/{id}Community
One actor with every known alias.
Exploit chains
GET /v1/chain/facts/{cve}Community
What exploiting the CVE requires and what it grants, from its CVSS vector: requires (for example none or low_priv), grants (for example code_exec, credentials), chain_role (for example entry) and the vector fields. basis says whether it came from CVSS v3 or v2.
GET /v1/chain/from/{cve}Developer
CVEs that could follow this one: candidate links from CVSS semantics and shared product. Add ?kev=1 to keep only known exploited next steps. These are marked derived: they could compose, not that anyone ran the sequence.
GET /v1/chain/validatedDeveloper
Named chains we have executed in the lab. Filter with ?platform=. Derived chains appear only with ?include_derived=1 and are labelled.
Board and lookup
GET /v1/boardCommunity
How the whole catalog splits across the four TRIS bands, plus every CVE in the ACT band, ranked. Refreshed daily. This is the data behind the threat intel board.
GET /v1/lookup/{id}Community
The CVE record, its documented actors and its chain facts in one response, as the website shows it: { "cve": ..., "actors": [...], "chain": ... }.
Bulk snapshots
GET /v1/bulk/manifestPro
Lists the newest snapshot of each dataset.
GET /v1/bulk/{dataset}Pro
Gzipped newline delimited JSON. Datasets: cves, actors, cve_actors, iocs, chain_facts. Load a snapshot once, then keep it current with GET /v1/cve?since=.
Score history
GET /v1/cve/{id}/historyScale
How a CVE's score moved over time: one entry per day it changed, each with day, tris_baseline, tris_band, epss_score and is_kev. ?days=90 sets the window, up to 365. Use it to show when a CVE crossed into ACT, or to back a trend line in your own reports.
curl -H "Authorization: Bearer $TRIX_KEY" \ "https://trix.cveasyai.com/v1/cve/CVE-2024-3094/history?days=180"
ACT alert webhooks
Once a day, after the board is published, we compare today's ACT band with yesterday's and POST the CVEs that entered it to each of your webhooks. Up to five webhooks per account.
POST /v1/webhooksScale
curl -X POST https://trix.cveasyai.com/v1/webhooks \
-H "Authorization: Bearer $TRIX_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://hooks.example.com/trix", "secret": "a long random string" }'
The url must be https on a public host name. IP address literals and private or internal ranges are refused. The secret is what we sign each delivery with; keep it on your server. Leave it out and we generate one and return it in this response only; we never show it again.
GET /v1/webhooksScale
Your webhooks and their status.
DELETE /v1/webhooks/{id}Scale
Stop deliveries to one webhook.
POST /v1/webhooks/{id}/testScale
Send a signed test delivery now, so you can check your endpoint and your signature code before the next board.
What we send
POST /trix HTTP/1.1
Content-Type: application/json
x-trix-signature: t=1790000000,v1=5f2b...e91c
{ "event": "act.entered", "cves": ["CVE-2026-1234", "CVE-2026-5678"], "board_date": "2026-09-28" }
v1 is the hex HMAC-SHA256, keyed with your secret, of the timestamp, a dot, and the raw request body: t + "." + body. Answer with any 2xx. A delivery that fails is tried three times with backoff, and a webhook that fails every day for seven days is switched off.
Verify the signature
Check it on the raw body, before you parse the JSON. Compare in constant time, and refuse a timestamp more than five minutes from your clock so a captured delivery cannot be replayed later.
# Python
import hashlib, hmac, time
def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
t, sig = parts.get("t", ""), parts.get("v1", "")
if not t.isdigit() or abs(time.time() - int(t)) > tolerance:
return False
expected = hmac.new(secret.encode(), t.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)
// Node 18 or later
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody, header, secret, tolerance = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = parts.t ?? "", sig = parts.v1 ?? "";
if (!/^\d+$/.test(t) || Math.abs(Date.now() / 1000 - Number(t)) > tolerance) return false;
const expected = createHmac("sha256", secret).update(`${t}.`).update(rawBody).digest("hex");
const a = Buffer.from(expected), b = Buffer.from(sig);
return a.length === b.length && timingSafeEqual(a, b);
}
In Express, read the body with express.raw({ type: "application/json" }) so rawBody is the exact bytes we signed.
Your usage
GET /v1/usageCommunity
Your key's calls per route per day for the last 30 days, and today's total against your daily limit:
{
"tier": "community",
"rate_per_min": 60,
"today": { "day": "2026-09-28", "used": 212, "limit_per_day": 1000 },
"usage": [ { "day": "2026-09-28", "route": "/v1/cve", "calls": 180 } ]
}
The account page shows the same numbers.
Errors
| Status | Meaning | What to do |
|---|---|---|
400 | Malformed request, for example a bad CVE ID | The body says which field. CVE IDs look like CVE-2024-3094. |
401 | No key, or the key is revoked or expired | Check the header, or issue a new key |
402 | The route or option needs a higher tier | The body names the tier that includes it, and where to upgrade |
403 | The key cannot reach this route | Use a read key; browser sessions from the website reach only the board and lookups |
404 | No such CVE, actor, or route | |
429 | Over the minute, day, or list records limit | Wait for Retry-After seconds, or upgrade to next_tier |
5xx | Our side | Retry with backoff; if it persists, write to us with the request id |
A 402 looks like this:
{
"error": "upgrade_required",
"feature": "score_history",
"required_tier": "scale",
"upgrade_url": "https://cveasyai.com/account/"
}
required_tier uses the internal tier names: developer, commercial (sold as Pro), scale, and enterprise. Errors are JSON with an error field and usually a reason or detail. Every response carries x-request-id; include it when you contact us and we can find the request.
Examples
Python
import os, time, requests
BASE = "https://trix.cveasyai.com"
S = requests.Session()
S.headers["Authorization"] = f"Bearer {os.environ['TRIX_KEY']}"
def get(path, **params):
while True:
r = S.get(BASE + path, params=params, timeout=30)
if r.status_code == 429:
time.sleep(int(r.headers.get("Retry-After", "60")))
continue
r.raise_for_status()
return r.json()
cve = get("/v1/cve/CVE-2021-44228")
print(cve["tris_baseline"], cve["tris_band"], cve["summary"])
# Every KEV CVE, page by page
cursor = None
while True:
# 50 is the Community page size; raise it to your tier's
page = get("/v1/cve", kev=1, limit=50, **({"cursor": cursor} if cursor else {}))
for c in page["results"]:
print(c["id"], c["tris_baseline"])
cursor = page["next_cursor"]
if not cursor:
break
JavaScript (Node 18 or later)
const BASE = "https://trix.cveasyai.com";
const headers = { Authorization: `Bearer ${process.env.TRIX_KEY}` };
const res = await fetch(`${BASE}/v1/actor/cve/CVE-2021-44228`, { headers });
if (!res.ok) throw new Error(`${res.status} ${res.headers.get("x-request-id")}`);
const { matches } = await res.json();
for (const m of matches) console.log(m.actor_name, m.match_type, m.ref_url ?? "");
Keep a local copy current
# Once a day: everything that changed since yesterday curl -H "Authorization: Bearer $TRIX_KEY" \ "https://trix.cveasyai.com/v1/cve?since=$(date -u -d yesterday +%F)&limit=50"
On macOS, use date -u -v-1d +%F for the date. Follow next_cursor until it is null, and raise limit to your tier's page size.
Terms and attribution
Community and Developer keys are for personal use, research, and protecting your own organization. Building the data into a product or a client service needs TRIX Pro. Showing TRIX data to your own customers inside your product needs TRIX Scale, and redistributing the data set itself needs TRIX Enterprise. Where you show TRIX data to others, credit it as "per CVEasy AI threat intel data" with a link to cveasyai.com/threat-intel/. EPSS scores come from FIRST, actor names from MITRE ATT&CK and the MISP galaxy, CVE records from NVD, and exploited status from CISA KEV. The full TRIX API terms apply.
Questions or a commercial use case: contact us. Your key and usage: account page.