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 weak ETag that identifies the body, so a client keeping its own copy can tell that copy is still current; no endpoint answers a conditional GET.
  • Floats-latest — URLs without a sha resolve the latest gominimal/pkgs:main commit at request time (Cache-Control: public, max-age=60, s-maxage=3600, stale-while-revalidate=86400).

Path parameters.

ParameterGrammarOn 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.

EndpointPurposeCache class
GET /api/pkgs/{name}.jsonFull per-package record at the latest commitFloats-latest
GET /api/pkgs/{name}/sbom.jsonSBOM at the latest commitFloats-latest
GET /api/pkgs/{name}/badge.svgScore/advisory SVG badge for READMEsFloats-latest
GET /api/pkgs/recent-commits.jsonLast N commits on pkgs:mainFloats-latest
GET /api/pkgs/bundle/latest.jsonRedirect to the current listing bundle (target needs a session)302 alias
GET /api/pkgs/bundle/{sha}.jsonListing-tier bundle at a shaSha-pinned, authenticated
GET /api/pkgs/bundle/{sha}/full.jsonFull-tier bundle at a shaSha-pinned, authenticated
GET /api/pkgs/bundle/{sha}/diff/{base}.jsonCatalog diff between two commitsSha-pinned, authenticated
GET /api/pkgs/bundle/{sha}/{name}/deps.jsonDirect-dependency rollup at a shaSha-pinned, authenticated
GET /api/pkgs/bundle/{sha}/{name}/versions.jsonVersion-history timeline at a shaSha-pinned, authenticated
GET /api/pkgs/bundle/{sha}/{name}/sbom.jsonReproducible sha-pinned SBOMSha-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:

![minimal score](https://minimal.dev/api/pkgs/openssl/badge.svg)

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.

SurfaceCache-Control
/api/pkgs/bundle/{sha}.json, /full.json, /diff/{base}.json, /{name}/deps.json, /{name}/versions.json, /{name}/sbom.jsonauth-gatedprivate, 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.jsonpublic, max-age=60, s-maxage=3600, stale-while-revalidate=86400
/api/pkgs/bundle/latest.json (redirect)public, max-age=60
/api/pkgs/openapi.jsonpublic, max-age=300, s-maxage=3600, stale-while-revalidate=86400
/pkgs/advisories.xmlpublic, max-age=60, s-maxage=3600, stale-while-revalidate=86400
/api/pkgs/* 4xx and short-prefix redirectsprivate, no-store (short prefix) or public, max-age=3600 (≥12 hex)