Packages API Reference
The /api/pkgs/* namespace is a read-only agent surface over the
minimal.dev package catalog. It exists for AI agents, integrations, and
curl-driven exploration — the webapp itself renders the same data
server-side, and both paths flow through one compose pipeline so they
cannot drift.
Every endpoint is GET. The floats-latest half is anonymous and
identity-invariant — no tokens, no cookies, the same body for every
caller — while the six sha-pinned endpoints require a signed-in session
and answer 401 without one. This page is the human-facing companion to the machine-readable
OpenAPI 3.1 document at /api/pkgs/openapi.json
and the /llms.txt agent index; the endpoint
inventory below is drift-guarded against that OpenAPI document so the two
can’t diverge.
The same catalog is also exposed as typed tools by the hosted
MCP server at /api/mcp — agents that speak the Model
Context Protocol can attach it by URL instead of calling these endpoints
directly.
Conventions
Base URL. All paths are relative to https://minimal.dev.
Authentication. A browser session cookie, and only for the six
sha-pinned endpoints under /api/pkgs/bundle/{sha}/…. There are no API
tokens yet, so a non-browser client authenticates by signing in at
/auth/login and replaying the session cookie. Everything else is
anonymous — see Auth and caching contract.
schemaVersion. Every JSON payload carries a schemaVersion field.
Compare it against the major version of the OpenAPI document before
trusting a cached body; a bump signals a breaking change to the payload
shape.
Cache classes. Responses fall into two families:
- Sha-pinned, authenticated — URLs under
/api/pkgs/bundle/{sha}/…are content-addressed by commit sha, so the body for a given URL never changes, but they are auth-gated and therefore uncacheable:Cache-Control: private, no-store. Each still carries a weakETagthat identifies the body, so a client keeping its own copy can tell that copy is still current; no endpoint answers a conditionalGET. - Floats-latest — URLs without a sha resolve the latest
gominimal/pkgs:maincommit at request time (Cache-Control: public, max-age=60, s-maxage=3600, stale-while-revalidate=86400).
Path parameters.
| Parameter | Grammar | On mismatch |
|---|---|---|
{name} | Package name: a leading alphanumeric, then alphanumerics plus ., _, - (case-insensitive). | 404 |
{sha}, {base} | Canonical 40-hex lowercase commit sha on gominimal/pkgs:main. | 404 unknown/malformed |
Short sha prefixes (7–39 hex chars) are accepted on the sha-pinned
endpoints: a unique prefix 301-redirects to the canonical 40-hex URL
(query params preserved), an ambiguous prefix returns 400 {"error":"ambiguous sha"}, and an unknown or malformed sha returns
404. The redirect is cached public, max-age=3600 for prefixes ≥12
hex chars and private, no-store for shorter ones.
JSON endpoints
Eleven endpoints make up the surface. The ten below are described by the
OpenAPI document; the eleventh is the document itself
(/api/pkgs/openapi.json).
The six sha-pinned endpoints require a signed-in session. Everything
under /api/pkgs/bundle/{sha}/… answers 401 with a
{"error":"auth_required","message":…} body unless the request carries a
session cookie; sign in at /auth/login to get one. No WWW-Authenticate
challenge is sent — the credential is a cookie, not an HTTP auth scheme.
The gate sits after sha resolution, so short shas still canonicalize exactly
as described above (301/400/404 are unchanged for every caller) and
only the payload needs the session. Both the 401 and the authenticated
200 are private, no-store. The floats-latest endpoints are unaffected,
and the MCP server cannot present a session yet, so its
sha-addressed calls return an error result.
| Endpoint | Purpose | Cache class |
|---|---|---|
GET /api/pkgs/{name}.json | Full per-package record at the latest commit | Floats-latest |
GET /api/pkgs/{name}/sbom.json | SBOM at the latest commit | Floats-latest |
GET /api/pkgs/{name}/badge.svg | Score/advisory SVG badge for READMEs | Floats-latest |
GET /api/pkgs/recent-commits.json | Last N commits on pkgs:main | Floats-latest |
GET /api/pkgs/bundle/latest.json | Redirect to the current listing bundle (target needs a session) | 302 alias |
GET /api/pkgs/bundle/{sha}.json | Listing-tier bundle at a sha | Sha-pinned, authenticated |
GET /api/pkgs/bundle/{sha}/full.json | Full-tier bundle at a sha | Sha-pinned, authenticated |
GET /api/pkgs/bundle/{sha}/diff/{base}.json | Catalog diff between two commits | Sha-pinned, authenticated |
GET /api/pkgs/bundle/{sha}/{name}/deps.json | Direct-dependency rollup at a sha | Sha-pinned, authenticated |
GET /api/pkgs/bundle/{sha}/{name}/versions.json | Version-history timeline at a sha | Sha-pinned, authenticated |
GET /api/pkgs/bundle/{sha}/{name}/sbom.json | Reproducible sha-pinned SBOM | Sha-pinned, authenticated |
GET /api/pkgs/{name}.json
Full composed record for one package — description, dependencies,
archive, full advisory payloads, and OpenSSF scorecard — wrapped in the
{schemaVersion, commitSha, pushedAt, package} envelope. Floats with the
latest gominimal/pkgs:main commit (the commitSha is resolved at
request time), so it is byte-identical to the <script id="pkgs-detail">
inline envelope on /pkgs/{name}. The transitiveAdvisories rollup is
omitted here — it is bundle-level. The endpoint that carries it,
/api/pkgs/bundle/{sha}/full.json,
requires a signed-in session.
404 on an unknown package name or a charset-violating segment.
curl -s https://minimal.dev/api/pkgs/jq.json | jq .package.name
GET /api/pkgs/{name}/sbom.json
Software bill of materials with the transitive build + runtime closure
and a vulnerabilities block, for agents that want “the SBOM as of now”.
The default format is CycloneDX 1.5
(application/vnd.cyclonedx+json); ?format=spdx returns SPDX 2.3 JSON
(application/spdx+json). Any other format value — including omitting
it — resolves to the CycloneDX default, and the endpoint never 400s;
the response Content-Type reflects the resolved format. Floats with the
latest commit. The sha-pinned sibling that gives a reproducible
point-in-time SBOM,
/api/pkgs/bundle/{sha}/{name}/sbom.json,
requires a signed-in session; this floating one is the
anonymous route.
404 on an unknown package name or a charset-violating segment.
GET /api/pkgs/{name}/badge.svg
A shields-style flat badge (image/svg+xml) for embedding in a README,
wiki, or runbook:

The default metric=score renders the package’s OpenSSF overall score
normalized to 0–100, colored by score band; ?metric=advisories renders
the active direct-advisory count, colored by max active severity. Any
other metric value — including omitting it — resolves to score, and
the endpoint never 400s. Composed through the same data path as
/api/pkgs/{name}.json, so the badge numbers
never drift from the JSON record. Floats with the latest commit.
404 on an unknown package name or a charset-violating segment.
GET /api/pkgs/recent-commits.json
The last N distinct commits on gominimal/pkgs:main, newest-first, each
with its first-line commit subject (message is null when
unrecoverable). Powers the “view at sha” picker. The ?limit= query
param defaults to 30 and is clamped into [1, 50]; non-numeric values
fall back to the default.
The endpoint degrades gracefully: an upstream error or a genuinely
empty history responds 200 [] with Cache-Control: no-store (never a
5xx), so an empty list is indistinguishable from a transient failure by
design. A non-empty success carries the floats-latest cache class.
GET /api/pkgs/bundle/latest.json
302-redirects to /api/pkgs/bundle/{sha}.json for the latest
gominimal/pkgs:main commit. The short-cached alias (Cache-Control: public, max-age=60) keeps edge storage bounded — follow the Location
header to fetch the bundle itself. 404 when vulndb has no commits
(preview env, fresh deploy).
The alias itself is anonymous, but its target is one of the six auth-gated
sha-pinned endpoints, so following the Location without a session answers
401.
GET /api/pkgs/bundle/{sha}.json
Requires a signed-in session — returns
401{"error":"auth_required"}without one. See JSON endpoints.
Card-only projection of every package at the given commit — name,
version, categories, stars, slim advisories — plus the derived
allCategories. Byte-identical to the <script id="pkgs-bundle"> inline
payload on /pkgs/search. For full per-package records at the same sha use
/api/pkgs/bundle/{sha}/full.json.
GET /api/pkgs/bundle/{sha}/full.json
Requires a signed-in session — returns
401{"error":"auth_required"}without one. See JSON endpoints.
Every composed package record at the given commit — description,
dependencies, archive, full advisory payloads, and the BFS-derived
transitiveAdvisories rollup that single-package compose paths can’t
compute. One request for everything at a snapshot instead of 1 + N
round-trips (listing bundle + per-package).
Each transitiveAdvisories entry is a de-duped
{ advisoryId, culpritPath } reference rather than an embedded advisory
object — at catalog scale one leaf culprit fans out across hundreds of
dependants, so the unique advisory objects ship once in the response’s
top-level advisories map (advisory id → full advisory object). Resolve
advisoryId through that map to reconstitute the advisory; culpritPath
stays per entry. When one advisory id affects multiple catalog packages
directly, the resolved object’s affectedPackage reflects an arbitrary
(first-seen, alphabetically-first) affected package and need not match
culpritPath.at(-1); read the culprit’s own affected version from that
culprit’s entry in the packages array. Per-package direct advisories
are unaffected.
GET /api/pkgs/bundle/{sha}/diff/{base}.json
Requires a signed-in session — returns
401{"error":"auth_required"}without one. See JSON endpoints.
What changed in the catalog from base (the older commit) to
sha (the newer commit): packages added and removed, version upgrades,
and advisory deltas (count changes and severity transitions, with
fromSeverity). This is the JSON twin of the /pkgs/changes HTML page —
both project through the same diffCatalog helper, so they can’t drift.
The (sha, base) pair is content-addressed.
GET /api/pkgs/bundle/{sha}/{name}/deps.json
Requires a signed-in session — returns
401{"error":"auth_required"}without one. See JSON endpoints.
The focal package’s direct build + runtime dependencies as of the
given commit, each enriched with the dependency’s own version, license,
and active-advisory rollup at that sha. Content-addressed by
(sha, name). 404 when the sha is unknown, a name segment violates the
charset, or the package has no lock-state row at this sha (unknown name,
or one that didn’t exist yet at this commit).
GET /api/pkgs/bundle/{sha}/{name}/versions.json
Requires a signed-in session — returns
401{"error":"auth_required"}without one. See JSON endpoints.
Every version the package has shipped up to (and including) the given
commit, newest-first, with per-version file/line diff stats and
dependency name sets. Content-addressed by (sha, name). 404 on an
unknown sha, a charset-violating name segment, or a name with no history
at this sha.
GET /api/pkgs/bundle/{sha}/{name}/sbom.json
Requires a signed-in session — returns
401{"error":"auth_required"}without one. See JSON endpoints.
The reproducible, sha-pinned variant of
/api/pkgs/{name}/sbom.json. Default format
is CycloneDX 1.5; ?format=spdx returns SPDX 2.3 JSON. (sha, name, format) is content-addressed — the ETag carries the format suffix so
conditional GETs can’t cross-talk between formats.
The OpenAPI document
GET /api/pkgs/openapi.json serves an OpenAPI 3.1 description of every
endpoint above. It is the machine-readable source of truth: the
components.schemas are generated from the same Zod wire schemas the
endpoints serve, and a drift-guard test asserts the documented path set
equals the route files under src/pages/api/pkgs/, so the document
cannot fall out of step with the code. Its info.version tracks the wire
schemaVersion (a major bump means a breaking change to every payload).
The document is prerendered — a pure function of the code with no data
dependencies — and cached public, max-age=300, s-maxage=3600, stale-while-revalidate=86400.
curl -s https://minimal.dev/api/pkgs/openapi.json | jq '.info.version, (.paths | keys)'
Other agent surfaces
The following surfaces are not described by the OpenAPI document
(which covers only /api/pkgs). They are documented here for
completeness because they serve the same agent and crawler audience.
Markdown mirrors (/pkgs/{name}.md)
Every package detail page has a plain-text markdown mirror at
/pkgs/{name}.md for machine consumers that prefer text over HTML. The
HTML detail page advertises it via <link rel="alternate" type="text/markdown">, and /llms.txt documents the pattern.
Package names may contain dots, so llama.cpp mirrors at
/pkgs/llama.cpp.md. Prerendered and served as a static file.
/llms.txt agent index
The site-wide agent index at /llms.txt follows the
llmstxt.org format: it links the main pages, the
read-only pkgs JSON API, and the markdown mirrors so AI agents and
crawlers can discover the surface. /sitemap.xml enumerates every
package name, which is the anonymous entry point for expanding the
{name} placeholder and crawling the per-package endpoints.
Advisory feed (/pkgs/advisories.xml)
An Atom 1.0 feed of the catalog’s direct security advisories,
newest-first. Point a feed reader (or a Slack RSS app) here to see new
advisories affecting the curated catalog without polling the JSON
endpoints. Unlike the sha-pinned bundles it floats with the latest
lock-state — subscribers see new advisories within the cache window
(public, max-age=60, s-maxage=3600, stale-while-revalidate=86400)
rather than waiting for a redeploy.
Auth and caching contract
Every surface on this page is read-only. The floats-latest endpoints, the
/pkgs/* markdown mirrors and feed, and /llms.txt are anonymous and safe
to cache: they are exempt from the auth middleware’s no-store rewrite that
would otherwise apply to a signed-in request, so a logged-in caller receives
the same cacheable response as an anonymous one.
The six sha-pinned endpoints are the exception. They are auth-gated, so both
the 401 and the authenticated 200 are private, no-store — a shared
cache holding either would let a stored body be served past the gate. Their
canonicalization arms answer every caller and keep their own posture: a
404 on a well-formed-but-unknown sha takes the short edge negative cache,
and a 301 from a prefix of ≥12 hex chars stays public, max-age=3600.
Other error responses (4xx) and short sha-prefix redirects carry private, no-store.
| Surface | Cache-Control |
|---|---|
/api/pkgs/bundle/{sha}.json, /full.json, /diff/{base}.json, /{name}/deps.json, /{name}/versions.json, /{name}/sbom.json — auth-gated | private, no-store on both the 401 and the authenticated 200 |
/api/pkgs/{name}.json, /api/pkgs/{name}/sbom.json, /api/pkgs/{name}/badge.svg, /api/pkgs/recent-commits.json | public, max-age=60, s-maxage=3600, stale-while-revalidate=86400 |
/api/pkgs/bundle/latest.json (redirect) | public, max-age=60 |
/api/pkgs/openapi.json | public, max-age=300, s-maxage=3600, stale-while-revalidate=86400 |
/pkgs/advisories.xml | public, max-age=60, s-maxage=3600, stale-while-revalidate=86400 |
/api/pkgs/* 4xx and short-prefix redirects | private, no-store (short prefix) or public, max-age=3600 (≥12 hex) |