REST API
This page covers /v1, the read-only public API for querying changes
(Understanding the data) programmatically.
Prerequisites
- All you need is
curl. No authentication, no API keys, no sign-up. - Limit: 1200 requests per minute per IP. Exceeding it returns
429with aRetry-Afterheader telling you how many seconds to wait before retrying. - All times are UTC; responses are JSON.
- The API describes itself: open https://ratatosk.io/v1 for every endpoint, parameter, and example.
The shape of a change
Everything here returns changes. One change is one thing a release did, carrying three axes you can filter and act on:
| Axis | Values | What it answers |
|---|---|---|
family |
security breaking deprecated |
What kind of thing is it — the axis a human subscribes by |
bucket |
action check plan other |
How to act now |
applies_if |
a boolean expression | Is it yours to act on |
bucket is the one to route on. action applies to every install of that
version. check applies only if applies_if matches your setup — resolve it
before you recommend anything. plan is announced for later. other is the
full record: bot dependency bumps and routine lines, excluded by default.
The server computes bucket with the same rule the website and the weekly
email use, so all three surfaces answer with the same number. Do not
recompute it from the other fields.
Endpoints
| Path | Purpose |
|---|---|
GET /v1 |
Self-describing index — every endpoint, parameter, and example |
GET /v1/changes |
Incremental sync feed. Filters: project family bucket actionability evaluable since limit. limit is 1–200 (default 50) |
GET /v1/changes/by-entity |
Reverse index: every change touching a single identifier (CVE, CRD, flag, …). Parameters: name (required), kind |
GET /v1/matters/{key} |
Every release in which one matter appeared, oldest first. ?include=all adds the note-level record |
GET /v1/projects |
Every tracked project. See "Project metadata" below for the fields |
GET /v1/releases/{project}/{version} |
One analyzed release: envelope plus all its changes. include=raw adds the original notes (raw_notes, with raw_notes_truncated: true when cut). The original comes with an attribution notice, raw_notes_notice — keep it as is if you republish the notes |
GET /v1/releases/{project} |
The project's latest analyzed release (version omitted) |
GET /v1/releases/{project}?limit=N |
Summaries of the N most recent releases — the newest-first query path (1–20; out of range is silently clamped) |
GET /v1/upgrade/{project}?from=&to= |
Everything taken on by an upgrade, folded per matter_key across release branches. to must be above from (equal or below is an empty range → 400); omit to for everything up to the latest. Versions are sent to the server — see "How much version information you send" below |
Examples
Every response on this page was captured on 2026-08-10 and trimmed for
length (… marks omissions). The data keeps growing, so your counts and
values will differ.
Security changes that apply to everyone:
curl "https://ratatosk.io/v1/changes?family=security&bucket=action&limit=2"
{
"changes": [
{
"change_id": "argo:v3.5.0:c140f331",
"matter_key": "argo/dependency/formidable#value_changed",
"project": "argo",
"version": "v3.5.0",
"version_rank": [3, 5, 0],
"family": "security",
"actionability": "act",
"bucket": "action",
"kind": "value_changed",
"applies_if": { "evaluable": false, "mode": "universal", "clauses": [], "raw": null },
"advisories": [],
"subjects": [ { "kind": "dependency", "name": "formidable", … } ],
"quote": "chore(deps): update dependency formidable to v2.1.3 [security]",
"source_url": "https://github.com/argoproj/argo-cd/releases/tag/v3.5.0",
"seq": 1415
},
… 1 more …
],
"next_since": 1421
}
Find every change covering a specific CVE — one call spans all projects:
curl "https://ratatosk.io/v1/changes/by-entity?name=CVE-2026-41178"
{
"changes": [
{
"change_id": "buildpacks:v0.40.9:83576398",
"matter_key": "buildpacks/advisory:cve-2026-41178",
"project": "buildpacks",
"version": "v0.40.9",
"family": "security",
"bucket": "action",
"advisories": [
{ "id": "CVE-2026-41178", "severity": "medium" },
{ "id": "GO-2026-5158", "severity": "medium" }
],
"subjects": [
{ "kind": "dependency", "name": "go.opentelemetry.io/otel", … },
{ "kind": "cve", "name": "cve-2026-41178", "name_full": "CVE-2026-41178", … }
],
…
}
]
}
Narrow an ambiguous name with kind — the values and their meanings are in
Understanding the data:
curl "https://ratatosk.io/v1/changes/by-entity?name=challenges.acme.cert-manager.io&kind=crd"
Summaries of the latest releases — each version comes with its counts by bucket and family:
curl "https://ratatosk.io/v1/releases/istio?limit=3"
{
"project": "istio",
"count": 3,
"releases": [
{
"version": "1.29.6",
"version_rank": [1, 29, 6],
"released_at": "2026-07-16T16:51:06.000Z",
"reviewed_at": "2026-08-09T04:33:14.694Z",
"changes_total": 0,
"by_bucket": {},
"by_family": {},
"max_severity": null,
"notes_total": 5,
"api_url": "https://ratatosk.io/v1/releases/istio/1.29.6"
},
… 2 more …
]
}
changes_total: 0 with notes_total: 5 is auditable silence: the release
was read, five lines were recorded, and none of them needs you. That is an
answer, not a gap.
Everything an upgrade takes on:
curl "https://ratatosk.io/v1/upgrade/istio?from=1.27.0&to=1.27.7"
{
"project": "istio",
"from": "1.27.0",
"to": "1.27.7",
"releases_covered": ["1.27.6", "1.27.7"],
"changes_total_before_dedupe": 5,
"changes": [
{
"change_id": "istio:1.27.7:8a66e585",
"matter_key": "istio/advisory:cve-2025-61732",
"family": "security",
"bucket": "action",
"kind": "defect_corrected",
"advisories": [ { "id": "CVE-2025-61732", "severity": "high" } ],
"quote": "- CVE-2025-61732 (CVSS score 8.6, High): A discrepancy between how Go and C/C++ …",
…
},
… more, ordered action → check → plan …
],
"privacy": "The versions sent in from/to reach this server (they are not written to access logs — …",
…
}
Tracking one matter across releases
matter_key is the identity of the underlying issue, stable across releases
and branches. /v1/matters/{key} returns every landing, oldest first —
not just the newest:
curl "https://ratatosk.io/v1/matters/containerd%2Fadvisory%3Acve-2026-47262"
Take the key verbatim from a change and URL-encode it: it is case-sensitive
and contains / and :.
Why every occurrence matters: the same containerd security roll-up landed on five branches carrying 2, 4 and 10 advisories respectively. Told only the newest, someone on the 2-advisory branch would assume they were fully covered.
Deciding whether a change is yours
applies_if is a boolean expression, not prose:
"applies_if": {
"evaluable": true,
"mode": "any_of",
"clauses": [
{ "kind": "api", "name": "ScheduleJobAlpha1", "verb": "uses", "polarity": "present" }
],
"raw": null
}
When evaluable is true, match the clauses against your own manifests
instead of parsing a sentence: mode is all_of, any_of, or universal
(applies to everyone), and each clause names a thing to look for. When
evaluable is false, raw carries the source sentence — we say so rather
than inventing structure we do not have.
Filter to only the machine-decidable ones with ?evaluable=true.
Judging severity
Severity lives on the cited advisories, not on the change: each entry in
advisories carries the ledger's current severity, re-read from the
official source rather than frozen at analysis time. Release summaries expose
the highest one as max_severity.
A change with advisories: [] is not "unrated" — it means the release note
cited no advisory id.
Incremental polling
/v1/changes is ordered by ascending seq — oldest analysis first — so
the first page is not the latest data. Pass the response's next_since as
since on the next request to receive only new changes:
# Round 1: from the beginning
curl "https://ratatosk.io/v1/changes?limit=100"
# → { "changes": [ ... ], "next_since": 295 }
# Round 2: the next_since you received, as since
curl "https://ratatosk.io/v1/changes?limit=100&since=295"
# → { "changes": [ ... ], "next_since": 398 }
next_since is the seq of the last change on that page — not a count and
not an offset. Sequence numbers are not contiguous, so never compute the
value; echo back exactly what you received. Repeat until next_since comes
back null, which means your local copy is up to date. Do not send
since=null; it returns 400.
Note that bucket=other rows are excluded unless you ask for them. They are
most of the record and mostly dependency bumps.
This cursor is for keeping a local copy in sync. For "the latest release of
X", use /v1/releases/{project}.
Project metadata
Each entry in GET /v1/projects carries slug, name, tier, category,
and analyzed_releases (the number of releases analyzed), plus these fields:
image_aliases: the other names this project's container images run under in a cluster. Use them to map a running image name back to a slug.cluster_core: true: marks cluster foundation components (control plane, datastore, DNS, runtime, CNI/dataplane). These are worth checking even when they are not in your stack.visibility: an observability hint present only on some entries. It tells you the component may not show up through the usual queries — etcd, for example, can run outside the Kubernetes API.
In a response:
{
"projects": [
…
{ "slug": "envoy", "name": "Envoy", "tier": "graduated",
"category": "networking", "analyzed_releases": 22,
"image_aliases": ["cilium-envoy"], "cluster_core": true },
…
],
"count": 76
}
How much version information you send
There is more than one way to answer "what do I take on by upgrading", and they differ in how much of your running configuration reaches the server. The full comparison is in MCP server. Within this API you have two options:
- Local range comparison: fetch changes and compare them yourself with
the
version_rankfield. No version leaves your machine.version_rankis a notation-independent array of numbers for ordering —v0.42.0becomes[0, 42, 0]. The rule: a change applies if its rank is greater than your current version's rank and at most the target version's rank. /v1/upgrade: the server computes the range for you. Thefrom/toversions reach the server, but they only pass through memory during processing and are not written to the access logs — the query is stripped before logging, the path is normalized to its prefix, and IPs are masked (privacy policy). The response'sprivacyfield states this handling explicitly.
Here is that comparison done by hand:
{ "change_id": "istio:1.27.7:8a66e585", "project": "istio",
"version": "1.27.7", "version_rank": [1, 27, 7], … }
If your version is [1, 27, 0] and the target is [1, 27, 7], then
[1, 27, 0] < [1, 27, 7] ≤ [1, 27, 7] — this change is in the range.
version_rank is null when a project's tag cannot be ordered numerically
(monorepo tags like flagd/v0.16.1, channel tags like lts-4081.3.8). Those
are excluded from range comparisons rather than guessed at.
Other machine-readable endpoints
/md(markdown negotiation): request the home page,/releases, or a release detail page with anAccept: text/markdownheader to receive markdown instead of HTML. Easy for agents to parse./.well-known/api-catalog: a machine-readable catalog of the APIs this site provides (RFC 9727)./.well-known/mcp/server-card.json: the MCP server card — the standard document agents use to discover the MCP server mechanically. See MCP server.
Troubleshooting
- 404 (version tags): projects differ in their
vprefix (envoy isv1.39.0, istio is1.28.9)./v1/releases/{project}/{version}accepts both notations, but if the tag is still wrong, the 404 response body carries the project's recently analyzed tags. Retry with an exact tag from that list. - 404 (matter key):
matter_keycontains/and:and is case-sensitive. URL-encode it and copy it verbatim from a change. - 429: you exceeded the 60-per-minute limit. Wait the number of seconds
in the
Retry-Afterheader, then retry. To cut the request count, fetch increments with thenext_sincecursor — far fewer requests than re-fetching everything each time. - Unknown project slug: check
GET /v1/projectsfirst. When the slug does not match the image name you see running, look the image name up inimage_aliases. - Empty result that looks wrong:
bucket=otheris excluded by default. Addactionability=noteorbucket=otherif you want the full record.
Terms of use
Analyses are AI-generated and provided without warranty. Every analysis links to the source it was drawn from. See the terms of service.