RATATOSKRATATOSK
Sign in

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 429 with a Retry-After header 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 seqoldest 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_rank field. No version leaves your machine. version_rank is a notation-independent array of numbers for ordering — v0.42.0 becomes [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. The from/to versions 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's privacy field 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 an Accept: text/markdown header 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 v prefix (envoy is v1.39.0, istio is 1.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_key contains / 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-After header, then retry. To cut the request count, fetch increments with the next_since cursor — far fewer requests than re-fetching everything each time.
  • Unknown project slug: check GET /v1/projects first. When the slug does not match the image name you see running, look the image name up in image_aliases.
  • Empty result that looks wrong: bucket=other is excluded by default. Add actionability=note or bucket=other if 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.