{
  "openapi": "3.1.0",
  "info": {
    "title": "QuenchWorks JSON API",
    "version": "1.0.0",
    "summary": "A free, read-only JSON API over the QuenchWorks catalog.",
    "description": "A free, read-only JSON API over the catalog of hardened, 0-CVE container images and signed Helm charts.\n\nIt is **prerendered as static JSON** (the dataset is small and the site is a static build), so it is fast, cacheable, and needs **no key**. All endpoints are `GET`, no auth, CORS-open, and cached for an hour (`cache-control: public, max-age=3600`).\n\nBase path: `/api/v1`.\n\n**Item fields** match what the catalog pages show (slug, name, category, tier, license, versions, ...). Each `images`/`charts` item also carries a live per-item `security` object from the nightly Trivy scan.\n\n**No `?query` filtering**: static hosting can't read query strings, so filter the collection client-side. Pagination lives in the path (`/api/v1/images/2.json`), not a `?page` query.",
    "contact": { "name": "QuenchWorks", "url": "https://quench-works.com" },
    "license": { "name": "MIT", "url": "https://github.com/quenchworks" }
  },
  "servers": [
    { "url": "https://quench-works.com", "description": "Production" }
  ],
  "tags": [
    { "name": "Collections", "description": "The full list for a resource under a small envelope." },
    { "name": "Pagination", "description": "One fixed-size page of a collection, with a meta + links envelope." },
    { "name": "Security", "description": "Live CVE counts from the nightly Trivy scan of each published image digest." },
    { "name": "Badges", "description": "shields.io endpoint badges for any README." }
  ],
  "paths": {
    "/api/v1/images.json": {
      "get": {
        "tags": ["Collections"],
        "summary": "All images",
        "description": "Every hardened container image in the catalog, each with a live per-item `security` rollup.",
        "operationId": "getImages",
        "responses": {
          "200": {
            "description": "The full images collection.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/CollectionEnvelope" },
                    { "type": "object", "properties": { "resource": { "const": "images" }, "data": { "type": "array", "items": { "$ref": "#/components/schemas/Image" } } } }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/charts.json": {
      "get": {
        "tags": ["Collections"],
        "summary": "All charts",
        "description": "Every signed Helm chart in the catalog, each with a live per-item `security` rollup.",
        "operationId": "getCharts",
        "responses": {
          "200": {
            "description": "The full charts collection.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/CollectionEnvelope" },
                    { "type": "object", "properties": { "resource": { "const": "charts" }, "data": { "type": "array", "items": { "$ref": "#/components/schemas/Chart" } } } }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/roadmap.json": {
      "get": {
        "tags": ["Collections"],
        "summary": "The roadmap",
        "description": "Apps on deck, not yet built.",
        "operationId": "getRoadmap",
        "responses": {
          "200": {
            "description": "The full roadmap collection.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/CollectionEnvelope" },
                    { "type": "object", "properties": { "resource": { "const": "roadmap" }, "data": { "type": "array", "items": { "$ref": "#/components/schemas/RoadmapItem" } } } }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{resource}/{page}.json": {
      "get": {
        "tags": ["Pagination"],
        "summary": "One page of a collection",
        "description": "One prerendered page of a collection (24 items each), with a meta + `links` envelope. The page number is part of the path — there is no `?page` query.",
        "operationId": "getResourcePage",
        "parameters": [
          {
            "name": "resource",
            "in": "path",
            "required": true,
            "description": "Which collection to page through.",
            "schema": { "type": "string", "enum": ["images", "charts", "roadmap"] }
          },
          {
            "name": "page",
            "in": "path",
            "required": true,
            "description": "1-based page number.",
            "schema": { "type": "integer", "minimum": 1, "default": 1 },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "One page of the collection.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PagedEnvelope" }
              }
            }
          }
        }
      }
    },
    "/api/v1/security.json": {
      "get": {
        "tags": ["Security"],
        "summary": "Security summary",
        "description": "The catalog-wide CVE summary: grand total, a severity rollup, and a per-image record (with per-version detail). Sourced from the QuenchWorks nightly Trivy scan of each published image digest, so it refreshes on every scan/deploy — never a hardcoded value.\n\nCounts only: each record carries an `href` to its full per-CVE detail at `/api/v1/security/{image}.json`.",
        "operationId": "getSecurity",
        "responses": {
          "200": {
            "description": "The security summary.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SecuritySummary" }
              }
            }
          }
        }
      }
    },
    "/api/v1/security/index.json": {
      "get": {
        "tags": ["Security"],
        "summary": "Per-image security index",
        "description": "Discovery index for the per-image detail endpoints: every scanned image with its grade, totals, and detail `href`.",
        "operationId": "getSecurityIndex",
        "responses": {
          "200": {
            "description": "The index of scanned images.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SecurityIndex" }
              }
            }
          }
        }
      }
    },
    "/api/v1/security/{image}.json": {
      "get": {
        "tags": ["Security"],
        "summary": "One image's full CVE detail",
        "description": "Everything the nightly scan knows about one image: the latest scanned version plus every published version, each with its own deduped `cves[]` (id, severity, package, installed and fixed version, title, advisory URL, affected targets). An image with no scan record is not published and returns 404.",
        "operationId": "getSecurityImage",
        "parameters": [
          { "name": "image", "in": "path", "required": true, "description": "Image slug, as used in the catalog (e.g. `opensearch`).", "schema": { "type": "string" }, "example": "opensearch" }
        ],
        "responses": {
          "200": {
            "description": "The image's full CVE detail.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SecurityImageDetail" }
              }
            }
          },
          "404": { "description": "No scan record for that image." }
        }
      }
    },
    "/api/v1/security/{image}/{version}.json": {
      "get": {
        "tags": ["Security"],
        "summary": "One version's CVE detail",
        "description": "One published version of one image, with that version's own `cves[]`. Versions containing dots or underscores (`3.7.0`, `1.0.0_beta11_p1`) are used verbatim in the path.",
        "operationId": "getSecurityImageVersion",
        "parameters": [
          { "name": "image", "in": "path", "required": true, "description": "Image slug.", "schema": { "type": "string" }, "example": "opensearch" },
          { "name": "version", "in": "path", "required": true, "description": "A published version of that image.", "schema": { "type": "string" }, "example": "3.7.0" }
        ],
        "responses": {
          "200": {
            "description": "That version's CVE detail.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SecurityVersionDetail" }
              }
            }
          },
          "404": { "description": "No scan record for that image/version." }
        }
      }
    },
    "/api/v1/badge/{stat}.json": {
      "get": {
        "tags": ["Badges"],
        "summary": "shields.io endpoint badge",
        "description": "A shields.io `endpoint` badge payload. Embed live in any README — shields fetches it on render and it refreshes each deploy:\n\n```md\n![images](https://img.shields.io/endpoint?url=https://quench-works.com/api/v1/badge/images.json)\n```\n\n`style`, `namedLogo`, and colors ship in the payload, so the for-the-badge look renders with no extra query params. `images` / `charts` / `cves` are live from the catalog data + scan snapshot; the rest are fixed project facts.",
        "operationId": "getBadge",
        "parameters": [
          {
            "name": "stat",
            "in": "path",
            "required": true,
            "description": "Which badge to render.",
            "schema": { "type": "string", "enum": ["images", "charts", "cves", "wolfi", "cosign", "multiarch", "artifacthub", "license"] },
            "example": "images"
          }
        ],
        "responses": {
          "200": {
            "description": "A shields.io endpoint-badge payload.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Badge" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "CollectionEnvelope": {
        "type": "object",
        "description": "The wrapper every full-collection response shares.",
        "required": ["apiVersion", "resource", "total", "data"],
        "properties": {
          "apiVersion": { "type": "string", "const": "v1" },
          "resource": { "type": "string", "enum": ["images", "charts", "roadmap"] },
          "total": { "type": "integer", "description": "Number of items in `data`." },
          "data": { "type": "array", "items": {} }
        }
      },
      "PagedEnvelope": {
        "type": "object",
        "description": "The wrapper for a single page of a collection.",
        "required": ["apiVersion", "resource", "page", "perPage", "total", "totalPages", "links", "data"],
        "properties": {
          "apiVersion": { "type": "string", "const": "v1" },
          "resource": { "type": "string", "enum": ["images", "charts", "roadmap"] },
          "page": { "type": "integer", "description": "This page's 1-based number." },
          "perPage": { "type": "integer", "const": 24 },
          "total": { "type": "integer", "description": "Total items across all pages." },
          "totalPages": { "type": "integer" },
          "links": {
            "type": "object",
            "required": ["self", "first", "last", "prev", "next", "collection"],
            "properties": {
              "self": { "type": "string" },
              "first": { "type": "string" },
              "last": { "type": "string" },
              "prev": { "type": ["string", "null"], "description": "null on the first page." },
              "next": { "type": ["string", "null"], "description": "null on the last page." },
              "collection": { "type": "string", "description": "The full-collection endpoint." }
            }
          },
          "data": { "type": "array", "items": {}, "description": "This page's items (Image, Chart, or RoadmapItem depending on `resource`)." }
        }
      },
      "ImageVersion": {
        "type": "object",
        "description": "One published tag of an image.",
        "required": ["version"],
        "properties": {
          "version": { "type": "string", "example": "5.4.3" },
          "size": { "type": "string", "description": "Human-readable image size.", "example": "15.6 MB" },
          "published": { "type": "string", "format": "date-time" },
          "digest": { "type": "string", "description": "Multi-arch index digest.", "example": "sha256:9a6993462f2d39621cc4f19da71e76ee6a2644b76f469f58754fec71e437ffc6" }
        }
      },
      "Image": {
        "type": "object",
        "description": "A hardened container image in the catalog.",
        "required": ["slug", "name", "category", "summary", "tier", "status", "license", "licenseClean", "upstream", "image"],
        "properties": {
          "slug": { "type": "string", "example": "adminer" },
          "name": { "type": "string", "example": "adminer" },
          "category": { "type": "string", "example": "Apps & productivity" },
          "summary": { "type": "string" },
          "tier": { "type": "string", "enum": ["critical", "standard", "low"] },
          "status": { "type": "string", "enum": ["available", "planned"] },
          "license": { "type": "string", "example": "Apache-2.0" },
          "licenseClean": { "type": "string", "enum": ["clean", "agpl", "caution"] },
          "version": { "type": "string", "description": "CSV of published tags, newest first (back-compat).", "example": "5.4.3, 5.2.1, 5.3.0" },
          "versions": { "type": "array", "items": { "$ref": "#/components/schemas/ImageVersion" } },
          "arches": { "type": "array", "items": { "type": "string" }, "example": ["amd64", "arm64"] },
          "upstream": { "type": "string", "format": "uri" },
          "source": { "type": "string", "format": "uri" },
          "image": { "type": "string", "description": "GHCR image reference.", "example": "ghcr.io/quenchworks/images/adminer" },
          "caution": { "type": "boolean" },
          "cleanAlternative": { "type": "string" },
          "knownIssue": { "type": "string", "description": "Editorial note about a current known issue (e.g. a CVE pending an upstream fix)." },
          "security": { "oneOf": [ { "$ref": "#/components/schemas/SecurityRecord" }, { "type": "null" } ], "description": "Live per-item CVE rollup from the nightly scan; null if the image has no scan record yet." }
        }
      },
      "ChartImageRef": {
        "type": "object",
        "required": ["name", "image"],
        "properties": {
          "name": { "type": "string", "example": "postgresql" },
          "image": { "type": "string", "description": "Digest-pinned image reference this chart deploys.", "example": "ghcr.io/quenchworks/images/postgresql@sha256:b492e8f2aa..." }
        }
      },
      "Chart": {
        "type": "object",
        "description": "A signed Helm chart in the catalog.",
        "required": ["slug", "name", "category", "summary", "tier", "license", "licenseClean", "chartVersion", "appVersion", "chartRef"],
        "properties": {
          "slug": { "type": "string", "example": "airflow" },
          "name": { "type": "string", "example": "airflow" },
          "category": { "type": "string", "example": "Workflow" },
          "summary": { "type": "string" },
          "tier": { "type": "string", "enum": ["critical", "standard", "low"] },
          "license": { "type": "string", "example": "Apache-2.0" },
          "licenseClean": { "type": "string", "enum": ["clean", "agpl", "caution"] },
          "chartVersion": { "type": "string", "example": "0.1.2" },
          "appVersion": { "type": "string", "example": "3.2.2" },
          "description": { "type": "string" },
          "imageRepository": { "type": "string", "example": "ghcr.io/quenchworks/images/airflow" },
          "imageDigest": { "type": "string", "example": "sha256:22b79f4f55..." },
          "repositoryID": { "type": "string", "description": "ArtifactHub repository ID.", "format": "uuid" },
          "port": { "type": "integer", "example": 8080 },
          "upstream": { "type": "string", "format": "uri" },
          "chartRef": { "type": "string", "description": "OCI reference for `helm install`.", "example": "oci://ghcr.io/quenchworks/charts/airflow" },
          "images": { "type": "array", "items": { "$ref": "#/components/schemas/ChartImageRef" }, "description": "Every image this chart deploys, digest-pinned." },
          "security": { "oneOf": [ { "$ref": "#/components/schemas/SecurityRecord" }, { "type": "null" } ], "description": "Live per-item CVE rollup for the chart's primary image; null if none yet." }
        }
      },
      "RoadmapItem": {
        "type": "object",
        "description": "An app on the roadmap, not yet built.",
        "required": ["name", "category", "priority"],
        "properties": {
          "name": { "type": "string", "example": "Vault" },
          "category": { "type": "string", "example": "Secrets & identity" },
          "license": { "type": "string", "example": "BUSL-1.1" },
          "licenseClean": { "type": "string", "enum": ["clean", "agpl", "caution"] },
          "priority": { "type": "string", "enum": ["next", "planned", "exploring"] },
          "note": { "type": "string" },
          "cleanAlternative": { "type": "string", "description": "An OSI-licensed alternative already shipped, when the app itself is source-available." }
        }
      },
      "SeverityRollup": {
        "type": "object",
        "description": "CVE counts by severity.",
        "required": ["critical", "high", "medium", "low", "unknown"],
        "properties": {
          "critical": { "type": "integer" },
          "high": { "type": "integer" },
          "medium": { "type": "integer" },
          "low": { "type": "integer" },
          "unknown": { "type": "integer" }
        }
      },
      "SecurityVersion": {
        "type": "object",
        "description": "The CVE rollup for one image tag.",
        "required": ["version", "tag", "critical", "high", "medium", "low", "unknown", "total", "fixable", "grade", "score"],
        "properties": {
          "version": { "type": "string" },
          "tag": { "type": "string" },
          "critical": { "type": "integer" },
          "high": { "type": "integer" },
          "medium": { "type": "integer" },
          "low": { "type": "integer" },
          "unknown": { "type": "integer" },
          "total": { "type": "integer" },
          "fixable": { "type": "integer", "description": "CVEs with an available fix — what a rebuild clears." },
          "grade": { "type": "string", "enum": ["A+", "A", "B", "C", "D", "F"] },
          "score": { "type": "integer", "minimum": 0, "maximum": 100 },
          "cves": { "type": "array", "description": "Per-CVE detail. Present on the /api/v1/security/{image}[/{version}].json endpoints; omitted from the counts-only rollup.", "items": { "$ref": "#/components/schemas/Cve" } }
        }
      },
      "Cve": {
        "type": "object",
        "description": "One finding, deduped across the targets it was seen in.",
        "required": ["id", "severity", "pkg", "installed", "fixed", "title", "url", "targets"],
        "properties": {
          "id": { "type": "string", "example": "CVE-2025-12345" },
          "severity": { "type": "string", "enum": ["CRITICAL", "HIGH", "MEDIUM", "LOW", "UNKNOWN"] },
          "pkg": { "type": "string", "description": "The package the finding is against." },
          "installed": { "type": ["string", "null"], "description": "Version currently shipped." },
          "fixed": { "type": ["string", "null"], "description": "Version that fixes it; null means no upstream fix yet (not fixable)." },
          "title": { "type": ["string", "null"] },
          "url": { "type": ["string", "null"], "description": "Primary advisory URL." },
          "targets": { "type": "array", "items": { "type": "string" }, "description": "Scan targets the finding was seen in (capped at 8)." },
          "targetsMore": { "type": "integer", "description": "Targets beyond the cap, when truncated." }
        }
      },
      "SecurityImageDetail": {
        "type": "object",
        "description": "One image's full CVE detail: latest version + every published version.",
        "required": ["apiVersion", "resource", "slug", "image", "source", "latest", "versions"],
        "properties": {
          "apiVersion": { "type": "string", "const": "v1" },
          "resource": { "type": "string", "const": "security" },
          "slug": { "type": "string", "example": "opensearch" },
          "image": { "type": "string", "example": "ghcr.io/quenchworks/images/opensearch" },
          "source": { "type": "string", "example": "QuenchWorks nightly Trivy scan (each published image digest)" },
          "latest": { "$ref": "#/components/schemas/SecurityVersion" },
          "versions": { "type": "array", "items": { "$ref": "#/components/schemas/SecurityVersion" } }
        }
      },
      "SecurityVersionDetail": {
        "type": "object",
        "description": "One version of one image, with that version's own CVE list.",
        "allOf": [
          { "$ref": "#/components/schemas/SecurityVersion" },
          {
            "type": "object",
            "properties": {
              "apiVersion": { "type": "string", "const": "v1" },
              "resource": { "type": "string", "const": "security" },
              "slug": { "type": "string" },
              "image": { "type": "string" },
              "source": { "type": "string" },
              "links": { "type": "object", "properties": { "image": { "type": "string", "example": "/api/v1/security/opensearch.json" } } }
            }
          }
        ]
      },
      "SecurityIndex": {
        "type": "object",
        "description": "Discovery index over the per-image detail endpoints.",
        "required": ["apiVersion", "resource", "source", "count", "images"],
        "properties": {
          "apiVersion": { "type": "string", "const": "v1" },
          "resource": { "type": "string", "const": "security" },
          "source": { "type": "string" },
          "count": { "type": "integer" },
          "images": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["slug", "image", "grade", "total", "fixable", "href"],
              "properties": {
                "slug": { "type": "string" },
                "image": { "type": "string" },
                "grade": { "type": "string", "enum": ["A+", "A", "B", "C", "D", "F"] },
                "total": { "type": "integer" },
                "fixable": { "type": "integer" },
                "href": { "type": "string", "example": "/api/v1/security/opensearch.json" }
              }
            }
          }
        }
      },
      "SecurityRecord": {
        "type": "object",
        "description": "The CVE rollup for one image (its latest tag), plus per-version detail.",
        "required": ["image", "version", "tag", "critical", "high", "medium", "low", "unknown", "total", "fixable", "grade", "score"],
        "properties": {
          "image": { "type": "string", "example": "ghcr.io/quenchworks/images/adminer" },
          "version": { "type": "string" },
          "tag": { "type": "string" },
          "critical": { "type": "integer" },
          "high": { "type": "integer" },
          "medium": { "type": "integer" },
          "low": { "type": "integer" },
          "unknown": { "type": "integer" },
          "total": { "type": "integer" },
          "fixable": { "type": "integer" },
          "grade": { "type": "string", "enum": ["A+", "A", "B", "C", "D", "F"] },
          "score": { "type": "integer", "minimum": 0, "maximum": 100 },
          "versions": { "type": "array", "items": { "$ref": "#/components/schemas/SecurityVersion" } },
          "href": { "type": "string", "description": "Link to this image's full per-CVE detail (present in the /api/v1/security.json rollup).", "example": "/api/v1/security/adminer.json" }
        }
      },
      "SecuritySummary": {
        "type": "object",
        "description": "The catalog-wide CVE summary.",
        "required": ["apiVersion", "resource", "source", "total", "fixable", "bySeverity", "imagesWithCves", "imagesNeedingRebuild", "images"],
        "properties": {
          "apiVersion": { "type": "string", "const": "v1" },
          "resource": { "type": "string", "const": "security" },
          "source": { "type": "string", "example": "QuenchWorks nightly Trivy scan (each published image digest)" },
          "total": { "type": "integer", "description": "Grand total of open CVEs catalog-wide." },
          "fixable": { "type": "integer", "description": "Open CVEs with an available fix." },
          "bySeverity": { "$ref": "#/components/schemas/SeverityRollup" },
          "imagesWithCves": { "type": "integer" },
          "imagesNeedingRebuild": { "type": "integer", "description": "Images with at least one fixable CVE." },
          "images": {
            "type": "object",
            "description": "Per-image records, keyed by image slug.",
            "additionalProperties": { "$ref": "#/components/schemas/SecurityRecord" }
          }
        }
      },
      "Badge": {
        "type": "object",
        "description": "A shields.io endpoint-badge payload.",
        "required": ["schemaVersion", "label", "message", "color"],
        "properties": {
          "schemaVersion": { "type": "integer", "const": 1 },
          "label": { "type": "string", "example": "images" },
          "message": { "type": "string", "example": "178 hardened" },
          "color": { "type": "string", "example": "0a0a0c" },
          "labelColor": { "type": "string", "example": "0a0a0c" },
          "style": { "type": "string", "example": "for-the-badge" },
          "logoColor": { "type": "string", "example": "white" },
          "namedLogo": { "type": "string", "example": "docker" }
        }
      }
    }
  }
}
