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 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
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.
| 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/{name}/attestation.json | Build attestations per architecture at the latest commit | 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/bundle/{sha}/{name}/attestation.json | Build attestations for what that commit shipped | 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}. 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:

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}.
map | Meaning |
|---|---|
present | The 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). |
pending | No map yet, and the commit is less than a day old. |
none-recorded | No 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. |
unavailable | The map could not be read. Nothing is claimed either way. |
Per-arch state | Meaning |
|---|---|
verified | A signed attestation exists, every signature checked out against the keys minimal.dev holds, and its subject matches the map’s artifact. |
signed-unchecked | Signed, 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. |
invalid | A signature failed, or the statement names a different artifact, package or architecture than the map. statement is null. |
unsigned | Only the unsigned statement exists. |
not-attested | The 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-map | The package is not built for this architecture at this commit. |
no-map | map is pending or none-recorded. |
unavailable | The 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.
| Surface | Cache-Control |
|---|---|
/api/pkgs/bundle/{sha}.json, /full.json, /diff/{base}.json, /{name}/deps.json, /{name}/versions.json, /{name}/sbom.json, /{name}/attestation.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/{name}/attestation.json | As 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.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) |