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 seven 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 seven 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

Fourteen endpoints make up the surface. The thirteen below are described by the OpenAPI document; the fourteenth is the document itself (/api/pkgs/openapi.json).

The seven 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/{name}/attestation.jsonBuild attestations per architecture at the latest commitFloats-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/bundle/{sha}/{name}/attestation.jsonBuild attestations for what that commit shippedSha-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}. scorecard.source says who ran the scan — depsdev and openssf-api are OpenSSF’s own published result (via deps.dev or OpenSSF’s API), ossf-self is Minimal running the OpenSSF Scorecard tool on a repository OpenSSF does not scan — and scorecard.repository names the GitHub repository that was scored, which is not always the package’s own. scorecard.overall is OpenSSF Scorecard’s 0–10 aggregate and scorecard.checks holds each check’s 0–10 score exactly as the tool reports it (-1 means not applicable or inconclusive). scorecard.date is when the scan ran; scorecard.dev may already hold a newer one. Nothing is regrouped or rescaled. 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:

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

The default metric=score renders the package’s OpenSSF Scorecard aggregate on its own 0–10 scale (5.6, as OpenSSF’s badge would show), labelled openssf scorecard and colored by Minimal’s band for the aggregate (red below 4, yellow to 7.9, green from 8); ?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/{name}/attestation.json

Build provenance for the artifacts the catalog ships, per architecture (amd64, arm64). Every artifact is addressable from the catalog commit through a per-commit package map; an artifact that a build run produced fresh also carries a signed attestation (a DSSE envelope around an in-toto Statement v1 in the SLSA provenance v1 format). The endpoint joins on that map, so the answer describes exactly the artifact commitSha ships.

curl -s https://minimal.dev/api/pkgs/jq/attestation.json | jq '.arches | map_values(.state)'

Top level: schemaVersion, commitSha, pushedAt, name, map, catalogVerification and arches.{amd64,arm64}.

mapMeaning
presentThe commit has a package map. The map itself never changes, and a per-arch state read from it is a settled fact — except unavailable (that envelope read failed and is retried on a later request) and invalid (a verdict about the envelope object’s bytes, which a replaced upload can change).
pendingNo map yet, and the commit is less than a day old.
none-recordedNo map, and the commit is a day old or more. Not permanent: a late map still turns this into present, which is why the answer is never cached for long.
unavailableThe map could not be read. Nothing is claimed either way.
Per-arch stateMeaning
verifiedA signed attestation exists, every signature checked out against the keys minimal.dev holds, and its subject matches the map’s artifact.
signed-uncheckedSigned, none invalid, but at least one signature could not be checked (reason: unknown-key or runtime-unsupported). This state does not imply that any signature was verified — an envelope whose only signature is from a key minimal.dev does not hold lands here too. Read signatures[].result to see what was actually checked.
invalidA signature failed, or the statement names a different artifact, package or architecture than the map. statement is null.
unsignedOnly the unsigned statement exists.
not-attestedThe artifact is in the map and has no attestation. Permanent for that build: attestations are written only when a run builds the artifact fresh.
not-in-mapThe package is not built for this architecture at this commit.
no-mapmap is pending or none-recorded.
unavailableThe attestation store could not be read, or did not answer within the server’s 5-second deadline. Nothing is claimed either way.

The state reflects the verifier this deployment runs: which public keys it holds and which algorithms its runtime supports. signed-unchecked can become verified after a key or algorithm is added, without any change upstream.

unavailable is transient by design. The first request for a commit the server has not read yet can time out against a slow upstream while the reads complete in the background, so retrying after a few seconds is the right response; the answer is sent private, no-store so nothing caches it.

One failure is outside that shape: if the catalog itself cannot be read — the endpoint cannot resolve which commit is latest — there is no commit to report a state for, so the endpoint answers 404 with private, no-store, the same status an unknown package name gets. Retry rather than concluding the package does not exist; the MCP get_attestation tool reports this case as a temporary error instead.

Each arch carries artifactSha256 and artifactUrl (the durable identifiers), specHash and envelopeUrl (point-in-time: spec hashes rotate), the parsed statement — subject, source commit, builder and its revision, run id and times, triggeredBy, resolvedDependencies — and a signatures array of {keyid, algorithm, result, reason}. Each dependency carries sha256Source: "statement" when the attestation itself names the built artifact, "map" when the attestation carried only the spec hash (a dependency built in the same run) and the digest was resolved from the same commit’s package map under that spec hash, and null with sha256: null when neither knows it. The statement’s source.commit is the commit that last rebuilt the package, which is usually older than commitSha.

catalogVerification is non-null for commits whose closure catalog carries Minimal’s production Sigstore signature. It names the catalog, its bundle and the certificate identity to pass to cosign verify-blob. That check covers the catalog for the commit, not this package’s artifact.

Signature checks are done by minimal.dev against public keys it pins; independent verification of the attestation signatures is not published yet.

404 on an unknown package name or a charset-violating segment. Cached floats-latest when the answer is final, public, max-age=60, s-maxage=300 while map is pending or an arch reads invalid — a verdict about the envelope’s bytes, which a replaced upload can change — and private, no-store whenever anything is unavailable.

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

GET /api/pkgs/bundle/{sha}/{name}/attestation.json

Requires a signed-in session — see JSON endpoints.

The sha-pinned variant of /api/pkgs/{name}/attestation.json: the same payload, answered from the package map of {sha}, so it describes the artifact that commit shipped rather than the latest one. A commit from before package maps were recorded answers map: "none-recorded".

404 on an unknown sha, a charset-violating name segment, or a package that commit does not hold. Short or uppercase shas 301 to the canonical URL.

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 seven 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.json, /{name}/attestation.json — auth-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/{name}/attestation.jsonAs the row above when the answer is final; public, max-age=60, s-maxage=300 while the commit’s map is pending or an arch reads invalid; private, no-store when anything is unavailable
/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)