Skip to content

upgradescope server API

Rendered from the OpenAPI 3.1.0 document api/openapi.yaml, which tests check against every route and handler response.

The REST API of upgradescope serve: snapshot ingest from agents, the read API behind the dashboard, the CI gate, auditor exports and cluster administration.

Authentication. Every credential is a bearer token (Authorization: Bearer <token>). Reads, the gate and /metrics take a read credential: serve --read-token, the admin token, or a read token minted with upgradescope tokens create --read; or, in the opt-in trusted-proxy mode (--trust-team-header with --trusted-proxy-cidr), a team header from a proxy in those ranges. With none configured, reads are open.

Read scopes. --read-token, the admin token and a read token minted for * read the whole fleet. A read token minted for teams, and the trusted proxy's header, read only those teams: the clusters one of them owns a namespace in (by the namespace attribution findings carry, after --team-map), and of those only their findings, suppressed findings (those the objects' upgradescope.basit.engineer/ignore annotations accepted, which the server's evaluations apply; never the agent's spec.ignore) and team scores. A finding that spans teams is cut to theirs: only their teams, namespaces and objects, its title counting only theirs and its detail replaced by one that names only what is kept, without the folded apiserver caller rows (callers). A scoped report has no unrecognizedImages, and the helm capability's reason and skipped list (which name releases as namespace/name) are withheld. Any other cluster answers the same 404 as an unknown one, on every per-cluster endpoint and for /api/v1/gate's ?cluster=, and is absent from the cluster list and fleet rollups. /metrics answers a team-scoped read 403. A scoped answer carries X-Upgradescope-Teams: <team,...> (each team percent-encoded, as a URL path segment is: Platform%20Team,payments) and Cache-Control: private, no-store; a fleet-wide one carries neither. In the trusted-proxy mode every read answer carries Vary: <header>, Authorization. A finding whose namespace list was capped is always cut for a scoped read. See https://abd-ulbasit.github.io/upgradescope/operations/auth/

Pushes take the shared ingest token (--ingest-token) or a per-cluster token from upgradescope tokens create. Deleting and renaming clusters take the admin token (--admin-token); without one the server refuses both. /healthz and /readyz are never authenticated.

Errors. Every error a handler writes is JSON, {"error": "..."}, with a 4xx or 5xx status. An unknown path under /api/, or below /healthz, /readyz, /livez or /metrics, is a JSON 404 too. A known path called with the wrong method gets ServeMux's 405 with an Allow header and a plain-text body. 500 bodies never carry internal detail: they read {"error": "internal error"} and the server logs the cause.

Targets are Kubernetes minors written MAJOR.MINOR (1.38). Where a target is optional, the default is the next minor above the version the cluster's latest snapshot reports.

Stability. Paths under /api/v1 keep their meaning within v1; fields may be added to responses, so the response schemas here do not forbid fields they do not list: ignore a field you do not know. See the compatibility policy: https://abd-ulbasit.github.io/upgradescope/compatibility-policy/

Authentication

Scheme Type Description
readToken bearer serve --read-token, the admin token, or a read token from upgradescope tokens create --read (fleet-wide, or scoped to teams). Not required when the server runs with no read credential configured.
ingestToken bearer serve --ingest-token, or a per-cluster token from upgradescope tokens create.
adminToken bearer serve --admin-token.

probes

Liveness, readiness and Prometheus metrics.

GET /healthz

Liveness. 200 while the process serves; never touches the database.

Auth: none.

Status Content type Schema Description
200 application/json Status The process is up.

GET /readyz

Readiness. 200 when the database answers a ping within 2s, else 503.

Auth: none.

Status Content type Schema Description
200 application/json Status The database answered.
503 application/json Error An error.

GET /metrics

Prometheus metrics. Prometheus text exposition: HTTP traffic, ingest results, and per cluster the score, verdict, blockers, push age and staleness of its current evaluations. Behind the read token when one is set. Its series name every cluster, so it takes a fleet-wide read credential: a team-scoped one is 403. The metric names are listed in the observability reference.

Auth: readToken (bearer).

Status Content type Schema Description
200 text/plain string Prometheus text format.
401 application/json Error An error.
403 application/json Error A team-scoped read credential.
503 application/json Error Reads of the whole fleet (/api/v1/clusters, /api/v1/fleet, /metrics) run two at a time, and this one waited more than 30s for its turn, or the responses waiting for their clients leave no room for this one (they share a budget of twice --max-snapshot-bytes with the per-cluster reads and /gate); retry after Retry-After. A response is sent with its Content-Length; one larger than that whole budget is sent while the next fleet read waits, and cut off if the client has not taken it within 20s.

ingest

Snapshot pushes from in-cluster agents.

POST /api/v1/snapshots

Push an inventory snapshot. What the agent sends every time its cluster's inventory changes and at least every --force-sync-every (1h). The body is JSON, optionally gzipped (Content-Encoding: gzip), at most serve --max-snapshot-bytes (20 MiB) on the wire and after decompression.

The server stores the inventory as sent and evaluates it against the cluster's next minor plus serve --targets, in one transaction. A push whose inventory equals the latest stored one (ignoring collectedAt and fields this server does not know) is a duplicate: 200, nothing new stored. A cluster name is bound to the first non-empty clusterId (kube-system namespace UID) pushed under it; from then on a push under that name from another UID, or with none, is a 409. A name registered without one is bound by the first clusterId pushed afterwards, from whichever cluster sends it, and that push continues the name's history: the server logs the adoption and sends no became-ready for it. Two pushers that both send no clusterId always share the name.

A per-cluster token may push only as its own cluster (403 before anything is written). clusterName is an RFC 1123 subdomain of at most 253 bytes, and the inventory's identifiers are what the apiserver accepts for what they name: namespaces RFC 1123 labels, object names at most 253 bytes without / or %, node and Helm release names RFC 1123 subdomains, team labels label values. Its other values are within the limits collectors keep to: strings at most 16 KiB (a capability's reason 64 KiB, an object's field manager 128 printable bytes), at most 100 objects per API usage entry, no count, objectsOmitted or namespace count below zero in an API usage entry (apiUsage, apiAuthorshipUnknown, a CRD's usage, a Helm release's manifestApis) or a volume plugin entry, each group/version/kind once per list, at most 200 unrecognized images and 32 capabilities. Anything else is 422, naming the field and the rule, before anything is stored. The free text a collector copies whole from the cluster is cut to the limits, not refused: a capability's reason and skipped entries, and an object's ignore (to its whole tokens) and ignoreReason (the upgradescope.basit.engineer/ignore annotations). The snapshot keeps the push as sent.

Each target's report is at most --max-snapshot-bytes: a push whose report for one would be larger is 413, and nothing is stored. No genuine inventory's report comes near it.

Memory is bounded by the body's structure as well as its size: while the body is read, its JSON values are counted (each value 1, each object 8) against a budget of 1,000,000 units, and buffered bodies share a budget of twice the cap across requests. One push is decoded at a time; a push that waits more than 10s for its turn, or finds the body budget full, gets 503 with Retry-After. So does a push whose notification baselines other writers (another replica's push, the background re-evaluation) replaced three times while it was evaluated; nothing is stored. The agent retries 408 and 503.

Auth: ingestToken (bearer).

Parameter In Type Required Description
Content-Encoding header gzip | identity no —

Request body (application/json): PushRequest

Status Content type Schema Description
200 application/json PushDuplicate A duplicate of the cluster's latest snapshot.
202 application/json PushAccepted Stored and evaluated.
401 application/json Error An error.
403 application/json Error An error.
400 application/json Error The body ended early or could not be read.
408 application/json Error The body did not arrive within the server's read timeout (60s for serve).
409 application/json Error An error.
413 application/json Error Over the byte cap (on the wire or after decompression) or over the node budget, before anything is decoded; or, once decoded, a report for one of the targets would be over --max-snapshot-bytes, and nothing is stored.
415 application/json Error An error.
422 application/json Error Not judgeable, refused before anything is written: invalid JSON or gzip, a body that is not valid UTF-8, an envelope schemaVersion other than 1, no clusterName or one that is not an RFC 1123 subdomain of at most 253 bytes, a missing or null inventory, an inventory schemaVersion other than 1, a source other than cluster, a collectorSchema other than 1 (or none), a serverVersion that is not a Kubernetes 1.x version, an identifier that is not valid for what it names, or a value beyond the limits collectors keep to (see above). The message names the field and the rule.
500 application/json Error An error.
503 application/json Error The shared body budget is full, the push waited too long for its turn, or other writers kept replacing its notification baselines (nothing is stored); retry after Retry-After.

clusters

Per-cluster state, reports, findings and history.

GET /api/v1/clusters

List clusters. Every cluster, with whether it is stale and the summary of its current evaluation at its default target (absent until one exists).

Auth: readToken (bearer).

Status Content type Schema Description
200 application/json array of ClusterSummary The clusters, by id.
401 application/json Error An error.
500 application/json Error An error.
503 application/json Error Reads of the whole fleet (/api/v1/clusters, /api/v1/fleet, /metrics) run two at a time, and this one waited more than 30s for its turn, or the responses waiting for their clients leave no room for this one (they share a budget of twice --max-snapshot-bytes with the per-cluster reads and /gate); retry after Retry-After. A response is sent with its Content-Length; one larger than that whole budget is sent while the next fleet read waits, and cut off if the client has not taken it within 20s.

GET /api/v1/clusters/{id}

Get one cluster. The cluster, the version and capabilities of its latest snapshot, and the summaries of its current evaluations: the default target and every applicable serve --targets minor.

Auth: readToken (bearer).

Parameter In Type Required Description
id path integer (int64) yes The cluster's numeric id (id in the cluster list). A non-integer is a 400.
Status Content type Schema Description
200 application/json ClusterDetail The cluster.
400 application/json Error An error.
401 application/json Error An error.
404 application/json Error An error.
503 application/json Error Reads that load a cluster's snapshot run one at a time, and this one waited more than 30s for its turn, or the responses waiting for their clients leave no room for this one (they share a budget of twice --max-snapshot-bytes with the fleet reads and /gate); retry after Retry-After. A response is sent with its Content-Length; one larger than that whole budget is sent while the next read waits, and cut off if the client has not taken it within 20s.

GET /api/v1/clusters/{id}/report

Readiness report. The full report for one target: the stored evaluation of the latest snapshot when there is one (source: stored), otherwise a what-if evaluated now from the latest snapshot and not stored (source: what-if), 413 when its report would be over --max-snapshot-bytes. teams holds per-team scores.

Auth: readToken (bearer).

Parameter In Type Required Description
id path integer (int64) yes The cluster's numeric id (id in the cluster list). A non-integer is a 400.
target query Target no Target minor. Default, the next minor above the cluster's version.
Status Content type Schema Description
200 application/json ReportResponse The report.
400 application/json Error An error.
401 application/json Error An error.
404 application/json Error An error.
413 application/json Error An error.
422 application/json Error An error.
503 application/json Error Reads that load a cluster's snapshot run one at a time, and this one waited more than 30s for its turn, or the responses waiting for their clients leave no room for this one (they share a budget of twice --max-snapshot-bytes with the fleet reads and /gate); retry after Retry-After. A response is sent with its Content-Length; one larger than that whole budget is sent while the next read waits, and cut off if the client has not taken it within 20s.

GET /api/v1/clusters/{id}/findings

Findings, filtered. The report's findings (same source as the report), exact-match filtered.

Auth: readToken (bearer).

Parameter In Type Required Description
id path integer (int64) yes The cluster's numeric id (id in the cluster list). A non-integer is a 400.
target query Target no Target minor. Default, the next minor above the cluster's version.
severity query Severity no Only findings of this severity. An unknown value matches nothing.
category query Category no Only findings of this category. An unknown value matches nothing.
Status Content type Schema Description
200 application/json FindingsResponse The findings.
400 application/json Error An error.
401 application/json Error An error.
404 application/json Error An error.
413 application/json Error An error.
422 application/json Error An error.
503 application/json Error Reads that load a cluster's snapshot run one at a time, and this one waited more than 30s for its turn, or the responses waiting for their clients leave no room for this one (they share a budget of twice --max-snapshot-bytes with the fleet reads and /gate); retry after Retry-After. A response is sent with its Content-Length; one larger than that whole budget is sent while the next read waits, and cut off if the client has not taken it within 20s.

GET /api/v1/clusters/{id}/history

Score history. Stored evaluations of the target, oldest first, within the retention window.

Auth: readToken (bearer).

Parameter In Type Required Description
id path integer (int64) yes The cluster's numeric id (id in the cluster list). A non-integer is a 400.
target query Target no Target minor. Default, the next minor above the cluster's version.
limit query integer no Most recent points to return, at most 1000.
Status Content type Schema Description
200 application/json array of ScorePoint The score history.
400 application/json Error An error.
401 application/json Error An error.
404 application/json Error An error.
422 application/json Error An error.
503 application/json Error Reads that load a cluster's snapshot run one at a time, and this one waited more than 30s for its turn, or the responses waiting for their clients leave no room for this one (they share a budget of twice --max-snapshot-bytes with the fleet reads and /gate); retry after Retry-After. A response is sent with its Content-Length; one larger than that whole budget is sent while the next read waits, and cut off if the client has not taken it within 20s.

GET /api/v1/clusters/{id}/teams

Per-team scores. The score formula applied to each team's findings, from the same report the report endpoint serves. Findings with no team are under (unattributed), a name no label value can take and --team-map refuses, so a real team called unattributed is a separate row.

Auth: readToken (bearer).

Parameter In Type Required Description
id path integer (int64) yes The cluster's numeric id (id in the cluster list). A non-integer is a 400.
target query Target no Target minor. Default, the next minor above the cluster's version.
Status Content type Schema Description
200 application/json TeamsResponse Team scores.
400 application/json Error An error.
401 application/json Error An error.
404 application/json Error An error.
413 application/json Error An error.
422 application/json Error An error.
503 application/json Error Reads that load a cluster's snapshot run one at a time, and this one waited more than 30s for its turn, or the responses waiting for their clients leave no room for this one (they share a budget of twice --max-snapshot-bytes with the fleet reads and /gate); retry after Retry-After. A response is sent with its Content-Length; one larger than that whole budget is sent while the next read waits, and cut off if the client has not taken it within 20s.

fleet

Rollups across every cluster.

GET /api/v1/fleet

Fleet score matrix. One row per cluster, one cell per target, from current stored evaluations only (nothing is recomputed). A cell is null when the cluster has no current evaluation for that target, or already runs it (then the target is also in the row's notApplicable).

Auth: readToken (bearer).

Parameter In Type Required Description
targets query string no Comma-separated minors, at most 16 distinct (more is 422). Default: every cluster's next minor plus the serve --targets minors some cluster does not run yet, at most 16 of them: those with the most clusters to fill them (the older minor on a tie), the rest counted in targetsOmitted.
Status Content type Schema Description
200 application/json FleetResponse The matrix.
401 application/json Error An error.
422 application/json Error An error.
500 application/json Error An error.
503 application/json Error Reads of the whole fleet (/api/v1/clusters, /api/v1/fleet, /metrics) run two at a time, and this one waited more than 30s for its turn, or the responses waiting for their clients leave no room for this one (they share a budget of twice --max-snapshot-bytes with the per-cluster reads and /gate); retry after Retry-After. A response is sent with its Content-Length; one larger than that whole budget is sent while the next fleet read waits, and cut off if the client has not taken it within 20s.

GET /api/v1/fleet/teams

Per-team rollup across the fleet. For one target, each team's worst score, total blockers and the clusters it has findings in. Each cluster contributes its stored evaluation, else a what-if from its latest snapshot (evaluated says which). Clusters left out are named in missing and, with the reason, in excluded: no-snapshot (the agent has not pushed one), too-large (the what-if report would be over --max-snapshot-bytes) or unreadable (the stored inventory does not decode). Clusters that already run the target are notApplicable.

A what-if's evaluatedAt is when it was computed: each cluster's contribution is kept for its snapshot (or its stored evaluation), the target, the server's knowledge base and team map and the UTC day, so a later request for the target computes only what changed. Each contribution is computed in the per-cluster read slot, one cluster at a time, so other per-cluster reads wait at most for one cluster's. A rollup still computing after 20s answers 503 with Retry-After; what it computed is kept, so the retry goes on from there.

A team's verdict is the worst of its verdicts in the clusters evaluated: blocked over unknown over ready. Its score and blockers count only its own findings, so a team with a clean score can still be blocked by a blocker no team owns, or unknown because a required check did not run.

Auth: readToken (bearer).

Parameter In Type Required Description
target query Target yes —
Status Content type Schema Description
200 application/json FleetTeamsResponse The rollup.
401 application/json Error An error.
422 application/json Error An error.
500 application/json Error An error.
503 application/json Error Reads that load a cluster's snapshot run one at a time, and this one waited more than 30s for its turn, or the responses waiting for their clients leave no room for this one (they share a budget of twice --max-snapshot-bytes with the fleet reads and /gate); retry after Retry-After. A response is sent with its Content-Length; one larger than that whole budget is sent while the next read waits, and cut off if the client has not taken it within 20s.

gate

The CI gate for rendered manifests.

POST /api/v1/gate

Gate rendered manifests. Evaluates a YAML (or JSON) manifest stream against a target and stores nothing. Without cluster, the manifests are judged alone, like scan --files. With cluster, they are judged inside that cluster's latest stored inventory (its version, nodes, add-ons, CRDs and team labels), with the manifests' objects, add-ons and CRDs merged into it, and the verdict counts only what the manifests introduce (findings tagged source: manifest); findings the cluster already has are tagged source: cluster. A removed or deprecated API a posted object uses, an add-on the manifests deploy, an in-tree volume plugin a posted pod template, Pod or PersistentVolume names (also when the cluster already has pods or volumes on it), and a custom resource they write at a version the CRDs do not serve are always the manifests'.

With a team-scoped credential, cluster is judged as the scope's share of it only (its teams' namespaces, and what describes the cluster as a whole): the findings, verdict, status, score and clusterVerdict are the share's, so a PR that breaks only another team's workloads passes. Repositories that can break shared or other teams' resources gate with a fleet-wide credential. See https://abd-ulbasit.github.io/upgradescope/operations/auth/#the-gate-with-a-team-scoped-token

fail-on works like scan --fail-on (default blocker): when an introduced finding reaches it, or the verdict is unknown, the status is 422 with the full body, so curl --fail-with-body fails a CI step and keeps the report. X-Upgradescope-Verdict always carries the verdict.

allow-incomplete=true is scan --allow-incomplete: the gate decides on findings alone. An unknown verdict (a required check did not run, such as a target past the knowledge base's horizon) with no finding at the fail-on threshold then answers 200, and a blocker still answers 422. The verdict header, the body's verdict and its required notAssessed gaps still say unknown. A target that is not an upgrade of the cluster (the target gap) is a mistake, not a coverage limit, and still fails. Only true and false are values, given once.

format picks the body: the JSON gate response (default), SARIF, JUnit XML whose test outcomes follow fail-on, or a GitLab Code Quality report. SARIF, JUnit and Code Quality hold only the findings the manifests introduce; the status and verdict header are the same in every format.

Suppression works as in scan: the upgradescope.basit.engineer/ignore and upgradescope.basit.engineer/ignore-reason annotations of posted (and, with cluster, stored) objects, and the ignore rules of a .upgradescope.yaml sent in config. Suppressed findings count toward neither the verdict nor fail-on; the JSON answer lists them in suppressed with suppressedCount, SARIF with no result for them (GitHub code scanning ignores a result's suppressions and would open an alert; one run-level note counts them instead), JUnit as skipped test cases, and Code Quality leaves them out. An expired rule no longer applies; it and an annotation without a reason are named in warnings. There is no baseline input: with cluster, the cluster's own findings are the baseline (source: cluster), and manifest-level debt is accepted with scan --baseline or ignore rules.

Limits: serve --max-gate-bytes (10 MiB) per body, and for the stream with its YAML aliases expanded; 4 MiB per document with its aliases expanded, 20,000 documents, and a node budget of 400,000 units (each YAML node 1, each sequence entry 4, each alias what it names), counted from the raw bytes before anything is decoded; over any of them is 413. Buffered bodies share a budget of three times the cap. Measuring aliases, decoding and evaluating run one request at a time; a request that waits more than 30s for its turn, or finds the shared body budget full, gets 503 with Retry-After. A stream with a UTF-16 byte order mark is 422: send UTF-8.

The answer may take at most --max-gate-bytes too: an upper bound on it, in the asked format (every string at its dearest escape), is computed after the evaluation and before anything is encoded, and over the cap is 413, saying to split the stream. With cluster, the cluster's own findings and the objects they list count toward it. The answer is built before it is sent, with its Content-Length. Answers waiting for their clients share a budget of twice --max-snapshot-bytes with the per-cluster and fleet reads; an answer that does not fit what is left of it is 503 with Retry-After. (One larger than that whole budget, possible only when --max-gate-bytes is over twice --max-snapshot-bytes, is sent while the next request waits, and cut off if the client has not taken it within 20s.)

Auth: readToken (bearer).

Parameter In Type Required Description
target query Target yes —
cluster query string no A cluster name or id whose stored context the manifests are judged in.
format query json | sarif | junit | gitlab-codequality no —
fail-on query blocker | warning | never no —
allow-incomplete query true | false no true gates on findings alone: an unknown verdict with no finding at the fail-on threshold is 200, not 422. See the operation description.
path query string no The repository-relative file the stream was rendered to (deploy/rendered.yaml): clean, relative, valid UTF-8 without control characters, at most 512 bytes (every listed object carries it); otherwise 422. Introduced findings are then located in it in SARIF and Code Quality, so code scanning or the merge request widget shows them on the change.
config query string no A .upgradescope.yaml (its text, URL-encoded): ignore rules applied as scan --config applies them, file globs matched against path. An invalid config, one over 32 KiB, or config given more than once is a 422. The request line, and so this parameter, also counts toward the server's 64 KiB request-header limit: URL-encoding expands YAML, so a config under 32 KiB can exceed it, and the server then answers 431 with a plain-text body. A reverse proxy in front of the server may refuse a long request line sooner (ingress-nginx's default answers 414 above 8 KiB).

Request body (application/x-yaml): string

Request body (application/json): string

Status Content type Schema Description
200 application/json GateResponse or CodeQuality The gate passed.
200 application/sarif+json SARIF The gate passed.
200 application/xml JUnit The gate passed.
422 application/json GateResponse or CodeQuality or Error The gate failed (same body as 200), or the request is invalid (no target, bad format, fail-on, allow-incomplete, path or config, an invalid document separator, a UTF-16 stream, or an undecodable manifest stream: an Error body).
422 application/sarif+json SARIF The gate failed (same body as 200), or the request is invalid (no target, bad format, fail-on, allow-incomplete, path or config, an invalid document separator, a UTF-16 stream, or an undecodable manifest stream: an Error body).
422 application/xml JUnit The gate failed (same body as 200), or the request is invalid (no target, bad format, fail-on, allow-incomplete, path or config, an invalid document separator, a UTF-16 stream, or an undecodable manifest stream: an Error body).
400 application/json Error The target is not a Kubernetes 1.x minor at or above the oldest the knowledge base covers (1.16): 1.3, which is what YAML makes of an unquoted target: 1.30, would judge nothing and read ready, so it is refused, saying to quote the version. Not a 422, which is the gate's own verdict. Also: the body ended early or could not be read.
401 application/json Error An error.
404 application/json Error An error.
408 application/json Error The body did not arrive within the server's read timeout (60s for serve).
413 application/json Error Over the byte cap (for the body, or for the stream with its aliases expanded), the per-document size (aliases expanded), the document count, the node budget, or the answer's bound, or, with ?cluster=, a report over --max-snapshot-bytes; the message says which, and to split the stream.
415 application/json Error An error.
503 application/json Error The shared body budget is full, the request waited more than 30s for its turn, or the answers waiting for their clients leave no room for this one; retry after Retry-After.

exports

Auditor exports of a stored evaluation.

GET /api/v1/clusters/{id}/export

Auditor export. The stored evaluation of the latest snapshot as CSV (one summary row, one row per finding, one suppressed row per finding or objects of one that the objects' ignore annotations accepted, with its severity, source, reason and objects in the detail, and one per capability not assessed) or a self-contained HTML report with the score history and a suppressed section. Never a what-if: 404 when no stored evaluation exists for the target, or when the cluster already runs it. An export is at most --max-snapshot-bytes, as the report it renders is (HTML writes ' " & as five bytes and < > as four): 413 above, saying to read the JSON report.

Auth: readToken (bearer).

Parameter In Type Required Description
id path integer (int64) yes The cluster's numeric id (id in the cluster list). A non-integer is a 400.
target query Target no Target minor. Default, the next minor above the cluster's version.
format query csv | html yes —
Status Content type Schema Description
200 text/csv string The export.
200 text/html string The export.
400 application/json Error An error.
401 application/json Error An error.
404 application/json Error An error.
413 application/json Error An error.
422 application/json Error An error.
503 application/json Error Reads that load a cluster's snapshot run one at a time, and this one waited more than 30s for its turn, or the responses waiting for their clients leave no room for this one (they share a budget of twice --max-snapshot-bytes with the fleet reads and /gate); retry after Retry-After. A response is sent with its Content-Length; one larger than that whole budget is sent while the next read waits, and cut off if the client has not taken it within 20s.

registry

The add-on registry this binary judges against.

GET /api/v1/registry

The add-on registry. The add-on EOL and compatibility dataset compiled into this binary, sorted by id.

Auth: readToken (bearer).

Status Content type Schema Description
200 application/json Registry The registry.
401 application/json Error An error.

admin

Cluster administration (admin token).

DELETE /api/v1/clusters/{id}

Delete a cluster. Removes the cluster with its snapshots, evaluations, queued notifications and the per-cluster ingest tokens minted for its name. An agent still pushing under the name registers it again.

Auth: adminToken (bearer).

Parameter In Type Required Description
id path integer (int64) yes The cluster's numeric id (id in the cluster list). A non-integer is a 400.
Status Content type Schema Description
204 — — Deleted.
400 application/json Error An error.
401 application/json Error An error.
403 application/json Error No admin token configured, or another token presented.
404 application/json Error An error.

PATCH /api/v1/clusters/{id}

Rename a cluster. Moves the cluster's history and per-cluster ingest tokens to the new name. Change the agent's --cluster-name too: until then its pushes under the old name are refused (per-cluster token) or register the old name again (shared token).

Auth: adminToken (bearer).

Parameter In Type Required Description
id path integer (int64) yes The cluster's numeric id (id in the cluster list). A non-integer is a 400.

Request body (application/json): RenameRequest

Status Content type Schema Description
200 application/json Cluster The renamed cluster.
400 application/json Error An error.
401 application/json Error An error.
403 application/json Error An error.
404 application/json Error An error.
409 application/json Error An error.
413 application/json Error An error.
422 application/json Error An error.

Schemas

Error

Every error a handler writes.

Field Type Required Description
error string yes —

Status

Field Type Required Description
status ok yes —

Target

A Kubernetes minor, MAJOR.MINOR, from 1.16 (the oldest the knowledge base covers; an older one is refused, with the gate's 400 and the other endpoints' 422). Quote it in YAML: an unquoted 1.30 is the number 1.3.

Type: string, pattern ^[0-9]+\.[0-9]+$.

Verdict

blocked: at least one blocker. unknown: no blocker found, but a required check was not assessed, so one may have been missed. ready: neither.

Type: ready, blocked, unknown.

Severity

Type: blocker, warning, info.

Category

One of removed-api, deprecated-api, deprecated-api-in-use, eol-addon, eol-approaching, version-skew, chart-incompat, kb-stale, addon-no-data, unknown-api, crd-version, support-lifecycle, volume-plugin. A release may add categories under /api/v1, so this is not an enum: treat one you do not know by its severity.

Type: string.

Cluster

Field Type Required Description
id integer (int64) yes —
name string yes The agent's --cluster-name.
clusterUid string yes The kube-system namespace UID the name is bound to.
firstSeen string (date-time) yes —
lastSeen string (date-time) yes The agent's last push, duplicates included.

ClusterFields

Field Type Required Description
id integer (int64) yes —
name string yes The agent's --cluster-name.
clusterUid string yes The kube-system namespace UID the name is bound to.
firstSeen string (date-time) yes —
lastSeen string (date-time) yes The agent's last push, duplicates included.

CapabilityGap

Something the evaluation could not assess.

Field Type Required Description
capability string yes A collector capability (api-usage, deprecated-calls, helm, addons, versions, crds, volumes), kb-coverage, or target (the target is not an upgrade of the cluster).
reason string yes —
partial boolean no The capability ran but did not read everything.
skipped array of string no What a partial capability did not read.
required boolean no The gap makes the verdict unknown.

SummaryGap

A report's gap as an evaluation summary carries it, bounded: its capability cut to 64 bytes, its reason to 256 and at most 3 of what it skipped, each cut to 128 bytes (a cut one ends in "…"), with skippedOmitted counting the rest. The report lists every gap whole.

Field Type Required Description
capability string yes A collector capability (api-usage, deprecated-calls, helm, addons, versions, crds, volumes), kb-coverage, or target (the target is not an upgrade of the cluster).
reason string yes —
partial boolean no The capability ran but did not read everything.
skipped array of string no What a partial capability did not read.
required boolean no The gap makes the verdict unknown.
skippedOmitted integer no Skipped entries not listed.

CapabilityStatus

Field Type Required Description
available boolean yes —
reason string no —
partial boolean no —
skipped array of string no —

EvalSummary

Field Type Required Description
target Target yes —
score integer yes —
ready boolean yes verdict == ready (kept for v0.1 clients).
verdict Verdict yes —
blockers integer yes —
warnings integer yes —
kbVersion string yes —
evaluatedAt string (date-time) yes When the result was last confirmed.
snapshotId integer (int64) yes —
outdated boolean no Evaluated before today (UTC), or under another knowledge base or team map; the next pass replaces it.
notAssessed array of SummaryGap no The required gaps first, then the rest. A cluster's detail lists every gap; /clusters lists at most 1 KiB of them (encoded) and counts the rest in notAssessedOmitted.
notAssessedOmitted integer no Gaps not listed (only in /clusters).

ClusterSummary

Field Type Required Description
id integer (int64) yes —
name string yes The agent's --cluster-name.
clusterUid string yes The kube-system namespace UID the name is bound to.
firstSeen string (date-time) yes —
lastSeen string (date-time) yes The agent's last push, duplicates included.
stale boolean yes No push within serve --stale-after.
latest EvalSummary no —

ClusterDetail

Field Type Required Description
id integer (int64) yes —
name string yes The agent's --cluster-name.
clusterUid string yes The kube-system namespace UID the name is bound to.
firstSeen string (date-time) yes —
lastSeen string (date-time) yes The agent's last push, duplicates included.
stale boolean yes —
serverVersion string no The version the latest snapshot is judged at.
capabilities map of CapabilityStatus no —
evaluations array of EvalSummary yes —

ObjectRef

Field Type Required Description
namespace string no —
name string no —
file string no Manifest path, relative to the scanned root (manifests only).
line integer no 1-based line of the object's apiVersion key (manifests only).
renderedFrom string no The Helm template the object was rendered from.
manager string no The field manager that wrote a live object through the flagged API, or kubectl last-applied.
ignore string no —
ignoreReason string no —

Finding

Field Type Required Description
category Category yes —
severity Severity yes —
key string no Stable, count-free identity: category/discriminator.
title string yes —
detail string yes —
teams array of string no —
namespaces array of string no Affected namespaces, sorted, at most 100.
namespacesOmitted integer no Affected namespaces not listed.
unnamespaced boolean no An add-on finding that also covers an install in no named namespace: a manifest object without metadata.namespace, or a cluster-scoped IngressClass. A namespace-scoped ignore rule cannot be shown to match it, so it does not take the finding.
remediation string no —
citations array of string (uri) no —
objects array of ObjectRef no —
objectsOmitted integer no Affected objects not listed.
baselineState new | unchanged no —
callers array of Caller no API usage findings: the apiserver_requested_deprecated_apis rows folded into this finding, in row order, each the deprecated-api-in-use finding it is on its own. Suppressing every object of the finding by annotation or an object-scoped rule does not suppress them: they then stand as findings of their own. A team-scoped read omits them from a finding it cuts to its teams.

FindingFields

Field Type Required Description
category Category yes —
severity Severity yes —
key string no Stable, count-free identity: category/discriminator.
title string yes —
detail string yes —
teams array of string no —
namespaces array of string no Affected namespaces, sorted, at most 100.
namespacesOmitted integer no Affected namespaces not listed.
unnamespaced boolean no An add-on finding that also covers an install in no named namespace: a manifest object without metadata.namespace, or a cluster-scoped IngressClass. A namespace-scoped ignore rule cannot be shown to match it, so it does not take the finding.
remediation string no —
citations array of string (uri) no —
objects array of ObjectRef no —
objectsOmitted integer no Affected objects not listed.
baselineState new | unchanged no —
callers array of Caller no API usage findings: the apiserver_requested_deprecated_apis rows folded into this finding, in row order, each the deprecated-api-in-use finding it is on its own. Suppressing every object of the finding by annotation or an object-scoped rule does not suppress them: they then stand as findings of their own. A team-scoped read omits them from a finding it cuts to its teams.

Caller

Field Type Required Description
group string no Absent for the core group.
version string yes —
resource string yes —
subresource string no —
key string yes The key of the caller's own deprecated-api-in-use finding.
severity Severity yes —
title string yes —
detail string yes —

SuppressedFinding

Field Type Required Description
category Category yes —
severity Severity yes —
key string no Stable, count-free identity: category/discriminator.
title string yes —
detail string yes —
teams array of string no —
namespaces array of string no Affected namespaces, sorted, at most 100.
namespacesOmitted integer no Affected namespaces not listed.
unnamespaced boolean no An add-on finding that also covers an install in no named namespace: a manifest object without metadata.namespace, or a cluster-scoped IngressClass. A namespace-scoped ignore rule cannot be shown to match it, so it does not take the finding.
remediation string no —
citations array of string (uri) no —
objects array of ObjectRef no —
objectsOmitted integer no Affected objects not listed.
baselineState new | unchanged no —
callers array of Caller no API usage findings: the apiserver_requested_deprecated_apis rows folded into this finding, in row order, each the deprecated-api-in-use finding it is on its own. Suppressing every object of the finding by annotation or an object-scoped rule does not suppress them: they then stand as findings of their own. A team-scoped read omits them from a finding it cuts to its teams.
reason string yes Why the finding is accepted.
source string yes What suppressed it: the config file, annotation, or spec.ignore.
expires string no YYYY-MM-DD, as the rule gave it.

TeamScore

Field Type Required Description
score integer yes —
ready boolean yes verdict == ready.
verdict ready | blocked | unknown no The team's readiness, by the report's rules over what can concern it: blocked by a blocker of its own or by an unattributed one (which cannot be ruled out as the team's); otherwise unknown when the report has a required not-assessed gap, which may hide any team's blocker; otherwise ready. Another team's blocker does not lower it; score, blockers and warnings count only the team's own findings.
blockers integer yes —
warnings integer yes —

TeamScores

Team name to score; findings with no team are under (unattributed), a name no label value can take and --team-map refuses, so a real team called unattributed is a separate row.

Type: map of TeamScore.

ReportEnvelope

The first two fields of every report-shaped response (a cluster's report and the gate's JSON answer), as of scan --output json.

Field Type Required Description
schemaVersion 1 yes The version of the report's shape. Within it fields are only added, never renamed, removed, retyped or given a new meaning; a breaking change bumps it (see the compatibility policy).
toolVersion string yes The server build that wrote the response, also for a stored evaluation. Informational, no compatibility meaning.

ReportFields

The engine's report, as scan --output json writes it.

Field Type Required Description
schemaVersion 1 yes The version of the report's shape. Within it fields are only added, never renamed, removed, retyped or given a new meaning; a breaking change bumps it (see the compatibility policy).
toolVersion string yes The server build that wrote the response, also for a stored evaluation. Informational, no compatibility meaning.
clusterId string yes —
target Target yes —
serverVersion string no The kube-apiserver gitVersion the target was judged against; absent for manifests.
kubeContext string no scan of a live cluster only: the kubeconfig context it read. The server never sets it.
apiServer string no scan of a live cluster only: the API server it read, as scheme, host and port, without credentials, path or query. The server never sets it.
kbVersion string yes —
score integer yes max(0, 100 - min(75, 25 x blockers) - min(20, 5 x warnings))
ready boolean yes verdict == ready.
verdict Verdict yes —
notAssessed array of CapabilityGap no —
suppressed array of SuppressedFinding no What ignore rules or object annotations accepted, out of findings, the score and the verdict. A stored or read-time report (GET /report, the fleet, history, notifications, exports) applies the objects' upgradescope.basit.engineer/ignore annotations (source annotation), never the agent's spec.ignore; /gate applies them and its config rules.
unrecognizedImages array of string no Image repositories (host/path) no add-on image matcher recognised: a detection gap, not a finding (neither score nor verdict counts them). Sorted, deduplicated, at most 200.
unrecognizedImagesOmitted integer no Unrecognized image repositories beyond the 200 listed.
addOnEvidenceAgeSeconds integer no How old, in seconds (at least 1), the pod evidence the add-ons were detected from was when the agent reused its last full pod pass instead of listing every pod again (--pod-pass-every, --pod-pass-max-age): an add-on installed or upgraded since is reported as it was then, and a pass of --pod-pass-max-age or more is not reused (the age is that of the collection, so a viewer can see evidence about one interval older). Absent when every pod was read for this report, and from collectors that predate the field. The verdict does not depend on it.
support SupportStatus no —
findings array of Finding yes —

ReportBase

The report's fields other than its findings.

Field Type Required Description
clusterId string yes —
target Target yes —
serverVersion string no The kube-apiserver gitVersion the target was judged against; absent for manifests.
kubeContext string no scan of a live cluster only: the kubeconfig context it read. The server never sets it.
apiServer string no scan of a live cluster only: the API server it read, as scheme, host and port, without credentials, path or query. The server never sets it.
kbVersion string yes —
score integer yes max(0, 100 - min(75, 25 x blockers) - min(20, 5 x warnings))
ready boolean yes verdict == ready.
verdict Verdict yes —
notAssessed array of CapabilityGap no —
suppressed array of SuppressedFinding no What ignore rules or object annotations accepted, out of findings, the score and the verdict. A stored or read-time report (GET /report, the fleet, history, notifications, exports) applies the objects' upgradescope.basit.engineer/ignore annotations (source annotation), never the agent's spec.ignore; /gate applies them and its config rules.
unrecognizedImages array of string no Image repositories (host/path) no add-on image matcher recognised: a detection gap, not a finding (neither score nor verdict counts them). Sorted, deduplicated, at most 200.
unrecognizedImagesOmitted integer no Unrecognized image repositories beyond the 200 listed.
addOnEvidenceAgeSeconds integer no How old, in seconds (at least 1), the pod evidence the add-ons were detected from was when the agent reused its last full pod pass instead of listing every pod again (--pod-pass-every, --pod-pass-max-age): an add-on installed or upgraded since is reported as it was then, and a pass of --pod-pass-max-age or more is not reused (the age is that of the collection, so a viewer can see evidence about one interval older). Absent when every pod was read for this report, and from collectors that predate the field. The verdict does not depend on it.
support SupportStatus no —

SupportStatus

A cluster on EKS, GKE or AKS whose Kubernetes minor the knowledge base dates: where that minor stands in the provider's support calendar, whether or not it is a finding (see the support-lifecycle category). Absent for other clusters and when the provider's dates for the minor are not known. The same for every target.

Field Type Required Description
provider eks | gke | aks yes The managed Kubernetes service the collector inferred from the server version suffix and the nodes' managed node pool labels.
minor string yes The control plane's Kubernetes minor.
phase standard | ending | extended | ended yes standard: standard support, not ending within 90 days. ending: standard support ends within 90 days. extended: past standard support, before the provider's extended window ends (EKS bills it by default; on GKE and AKS it is opt-in, see extendedSupportCondition, and the cluster may not be enrolled). ended: past the end of extended support, or past standard support for a minor with no extended support.
extendedSupportFrom string (date) yes The day (UTC) standard support ends and extended support begins. EKS bills extended support from the start of this day.
extendedSupportEnds string (date) no The day extended support ends; absent when the provider offered none for the minor.
annualCostDelta string no What extended support adds per cluster per year at the provider's published list price, (extended - standard) x 8760 cluster-hours, as a decimal string in currency. A list price as of priceAsOf, not the customer's bill: prices change and vary by contract. Present only when the knowledge base cites the provider's price and the minor can still be in extended support (not in phase ended); never inferred.
currency USD no The currency of annualCostDelta.
priceAsOf string (date) no The day the list price behind annualCostDelta was read from the provider's pricing page.
annualCostNote string no The provider's caveat on whom annualCostDelta is charged to (GKE: only clusters on the Extended release channel). Present with annualCostDelta when the knowledge base records one; show it beside the figure.
extendedSupportCondition string no Where the provider's extended support is opt-in (GKE, AKS): the configuration under which it applies, as a clause completing "only if ..." ("Long Term Support is enabled"). The scanner cannot see the cluster's configuration, so phases ending and extended describe the provider's window, not a confirmed enrolment. Absent for EKS and when the provider offered no extended support for the minor.

ReportMeta

Field Type Required Description
evaluatedAt string (date-time) yes —
snapshotId integer (int64) yes —
source stored | what-if yes —
serverVersion string no —
notApplicable boolean no The cluster already runs the target.
outdated boolean no —

ReportResponse

Field Type Required Description
schemaVersion 1 yes The version of the report's shape. Within it fields are only added, never renamed, removed, retyped or given a new meaning; a breaking change bumps it (see the compatibility policy).
toolVersion string yes The server build that wrote the response, also for a stored evaluation. Informational, no compatibility meaning.
clusterId string yes —
target Target yes —
serverVersion string no —
kubeContext string no scan of a live cluster only: the kubeconfig context it read. The server never sets it.
apiServer string no scan of a live cluster only: the API server it read, as scheme, host and port, without credentials, path or query. The server never sets it.
kbVersion string yes —
score integer yes max(0, 100 - min(75, 25 x blockers) - min(20, 5 x warnings))
ready boolean yes verdict == ready.
verdict Verdict yes —
notAssessed array of CapabilityGap no —
suppressed array of SuppressedFinding no What ignore rules or object annotations accepted, out of findings, the score and the verdict. A stored or read-time report (GET /report, the fleet, history, notifications, exports) applies the objects' upgradescope.basit.engineer/ignore annotations (source annotation), never the agent's spec.ignore; /gate applies them and its config rules.
unrecognizedImages array of string no Image repositories (host/path) no add-on image matcher recognised: a detection gap, not a finding (neither score nor verdict counts them). Sorted, deduplicated, at most 200.
unrecognizedImagesOmitted integer no Unrecognized image repositories beyond the 200 listed.
addOnEvidenceAgeSeconds integer no How old, in seconds (at least 1), the pod evidence the add-ons were detected from was when the agent reused its last full pod pass instead of listing every pod again (--pod-pass-every, --pod-pass-max-age): an add-on installed or upgraded since is reported as it was then, and a pass of --pod-pass-max-age or more is not reused (the age is that of the collection, so a viewer can see evidence about one interval older). Absent when every pod was read for this report, and from collectors that predate the field. The verdict does not depend on it.
support SupportStatus no —
findings array of Finding yes —
evaluatedAt string (date-time) yes —
snapshotId integer (int64) yes —
source stored | what-if yes —
notApplicable boolean no The cluster already runs the target.
outdated boolean no —
teams TeamScores no —

FindingsResponse

Field Type Required Description
target Target yes —
findings array of Finding yes —
evaluatedAt string (date-time) yes —
snapshotId integer (int64) yes —
source stored | what-if yes —
serverVersion string no —
notApplicable boolean no The cluster already runs the target.
outdated boolean no —

TeamsResponse

Field Type Required Description
target Target yes —
teams TeamScores yes —
evaluatedAt string (date-time) yes —
snapshotId integer (int64) yes —
source stored | what-if yes —
serverVersion string no —
notApplicable boolean no The cluster already runs the target.
outdated boolean no —

ScorePoint

Field Type Required Description
at string (date-time) yes —
score integer yes —
ready boolean yes —
verdict Verdict no The evaluation's verdict. A score can't tell unknown from ready (an evaluation whose required check did not run can score 100), so draw the trend by this. Absent from servers that predate it.

FleetCell

Field Type Required Description
score integer yes —
ready boolean yes —
verdict Verdict yes —
blockers integer yes —
evaluatedAt string (date-time) yes —
snapshotId integer (int64) yes —
source stored yes —
outdated boolean no —
notAssessed array of SummaryGap no The required gaps first, then the rest, at most 1 KiB of them (encoded).
notAssessedOmitted integer no Gaps not listed.

FleetRow

Field Type Required Description
clusterId integer (int64) yes —
name string yes —
lastSeen string (date-time) yes —
stale boolean yes —
serverVersion string no —
cells map of FleetCell or null yes Target to cell; null when there is no current evaluation or the target is not applicable.
notApplicable array of Target no —

FleetResponse

Field Type Required Description
targets array of Target yes —
targetsOmitted integer no Default columns left out past the 16 (absent with ?targets= and when none are); ask for them with ?targets=.
clusters array of FleetRow yes —

FleetTeam

Field Type Required Description
worstScore integer yes —
blockers integer yes —
verdict Verdict yes The worst of the team's verdicts across the clusters, blocked over unknown over ready. Never read the score without it.
clusters array of string yes —

FleetExcluded

Field Type Required Description
name string yes —
clusterId integer (int64) yes —
reason no-snapshot | too-large | unreadable yes no-snapshot: the agent has not pushed one. too-large: the cluster has snapshots, but its report for the target would be over --max-snapshot-bytes. unreadable: its stored inventory does not decode (or the cluster went away mid-request).

FleetTeamsSource

Field Type Required Description
name string yes —
clusterId integer (int64) yes —
source stored | what-if yes —
evaluatedAt string (date-time) yes —
snapshotId integer (int64) yes —

FleetTeamsResponse

Field Type Required Description
target Target yes —
teams map of FleetTeam yes —
evaluated array of FleetTeamsSource yes —
missing array of string yes The names of every cluster left out of the rollup, whatever the reason; excluded says why.
excluded array of FleetExcluded yes —
notApplicable array of string yes —

GateFinding

Field Type Required Description
category Category yes —
severity Severity yes —
key string no Stable, count-free identity: category/discriminator.
title string yes —
detail string yes —
teams array of string no —
namespaces array of string no Affected namespaces, sorted, at most 100.
namespacesOmitted integer no Affected namespaces not listed.
unnamespaced boolean no An add-on finding that also covers an install in no named namespace: a manifest object without metadata.namespace, or a cluster-scoped IngressClass. A namespace-scoped ignore rule cannot be shown to match it, so it does not take the finding.
remediation string no —
citations array of string (uri) no —
objects array of ObjectRef no —
objectsOmitted integer no Affected objects not listed.
baselineState new | unchanged no —
callers array of Caller no API usage findings: the apiserver_requested_deprecated_apis rows folded into this finding, in row order, each the deprecated-api-in-use finding it is on its own. Suppressing every object of the finding by annotation or an object-scoped rule does not suppress them: they then stand as findings of their own. A team-scoped read omits them from a finding it cuts to its teams.
source manifest | cluster yes manifest: introduced by the posted manifests; cluster: the cluster already has it.

GateResponse

Field Type Required Description
schemaVersion 1 yes The version of the report's shape. Within it fields are only added, never renamed, removed, retyped or given a new meaning; a breaking change bumps it (see the compatibility policy).
toolVersion string yes The server build that wrote the response, also for a stored evaluation. Informational, no compatibility meaning.
clusterId string yes —
target Target yes —
serverVersion string no The kube-apiserver gitVersion the target was judged against; absent for manifests.
kubeContext string no scan of a live cluster only: the kubeconfig context it read. The server never sets it.
apiServer string no scan of a live cluster only: the API server it read, as scheme, host and port, without credentials, path or query. The server never sets it.
kbVersion string yes —
score integer yes max(0, 100 - min(75, 25 x blockers) - min(20, 5 x warnings))
ready boolean yes verdict == ready.
verdict Verdict yes —
notAssessed array of CapabilityGap no —
suppressed array of SuppressedFinding no What ignore rules or object annotations accepted, out of findings, the score and the verdict. A stored or read-time report (GET /report, the fleet, history, notifications, exports) applies the objects' upgradescope.basit.engineer/ignore annotations (source annotation), never the agent's spec.ignore; /gate applies them and its config rules.
unrecognizedImages array of string no Image repositories (host/path) no add-on image matcher recognised: a detection gap, not a finding (neither score nor verdict counts them). Sorted, deduplicated, at most 200.
unrecognizedImagesOmitted integer no Unrecognized image repositories beyond the 200 listed.
addOnEvidenceAgeSeconds integer no How old, in seconds (at least 1), the pod evidence the add-ons were detected from was when the agent reused its last full pod pass instead of listing every pod again (--pod-pass-every, --pod-pass-max-age): an add-on installed or upgraded since is reported as it was then, and a pass of --pod-pass-max-age or more is not reused (the age is that of the collection, so a viewer can see evidence about one interval older). Absent when every pod was read for this report, and from collectors that predate the field. The verdict does not depend on it.
support SupportStatus no —
findings array of GateFinding yes —
teams TeamScores no —
clusterVerdict Verdict no With cluster, the verdict of the cluster plus the manifests, existing findings included; with a team-scoped credential, of the scope's share of the cluster plus the manifests.
suppressedCount integer yes The number of suppressed entries.
warnings array of string no Suppression warnings: rules in config that expired (they no longer apply), upgradescope.basit.engineer/ignore annotations without a reason (not applied), and one line for the posted objects annotated under the deprecated pre-v0.2.0 keys (applied, and read until v0.3.0), naming the first five. Stored objects (cluster) under the old keys are not named here: the cluster's agent reports them in its ClusterReadiness status.notAssessed.

SARIF

A SARIF 2.1.0 log (https://json.schemastore.org/sarif-2.1.0.json) with one run, the report's findings as results.

Type: object.

JUnit

JUnit XML valid against the Jenkins JUnit schema (the xUnit plugin's junit-10.xsd): one testsuite per finding category with one testcase per finding, failing when the finding reaches fail-on; a not-assessed suite with an error per required gap the gate fails on. See scan --output junit.

Type: string.

CodeQuality

A GitLab Code Quality report (https://docs.gitlab.com/ci/testing/code_quality/#code-quality-report-format): one entry per finding and located object. Findings without a file are on the virtual path upgradescope/<finding key>. See scan --output gitlab-codequality.

Type: array of object.

PushRequest

Field Type Required Description
schemaVersion 1 yes The push protocol version.
clusterName string yes —
agentVersion string no —
kbVersion string no —
inventory Inventory yes —

Inventory

What the agent observed, never a verdict. The server stores it as sent, unknown fields included, so a newer server can judge them.

Field Type Required Description
schemaVersion 1 yes —
clusterId string no The kube-system namespace UID.
collectorSchema integer no The generation of field meanings the collector filled this inventory with (1 from v0.2.0). Absent from inventories of collectors before it — v0.1.x and v0.2.0's release candidates — which are judged by the push's agentVersion: one that is not a release at or after 0.2.0-0 (including "dev" and empty) is a v0.1.x agent, whose api-usage and deprecated-calls are not assessed and whose chart-found add-on version is evidence only. A later generation, whose meanings this server would misread, is 422.
source cluster no Absent from v0.1.x agents' inventories, which are cluster ones too. A files inventory (scan --files) is judged without versions or add-ons, so a push claiming one is 422: only the agent pushes, and it collects from a cluster. A files snapshot stored before ingest refused them is judged as a cluster one.
collectedAt string (date-time) no —
serverVersion string no The apiserver's gitVersion, e.g. v1.34.2-gke.100.
capabilities map of CapabilityStatus no Every capability the collector has, by name. One the verdict requires and the map does not report — api-usage, versions, and addons when the knowledge base has add-ons — is a required not-assessed gap, so an inventory without this map is never ready.
apiUsage array of object no —
apiAuthorshipUnknown array of object no Objects of a flagged API with no managedFields entry outside the status subresource and the control plane, and no usable last-applied annotation, so nothing says which version wrote them; shaped like apiUsage, once per kind. Reported as info, never as use of the API.
deprecatedCalls array of object no —
helmReleases array of object no —
gitopsCharts array of object no Charts Argo CD (Application spec.source/spec.sources[] entries with chart set) and Flux (HelmRelease spec.chart.spec, or a chartRef to an OCIRepository) deploy to the scanned cluster, as those resources name them. Each has tool (argocd or flux), name and namespace (the Application's or HelmRelease's own), target (the namespace the chart deploys into; omitted when left to the manifests), chart, version (as the resource spells it, so possibly a constraint or a tag; omitted when none) and repo (an Application's repoURL or an OCIRepository's URL, recorded without userinfo, query or fragment, though a token embedded in the path is kept; for a HelmRelease's own source, Kind/namespace/name). Applications and HelmReleases that deploy to other clusters, or to a target that is not a namespace name, are not listed. A chart reference carries no app version and no stored manifest; a Helm release that Flux's helm-controller leaves is listed in helmReleases.
addOns array of object no —
nodes array of object no —
controlPlane array of object no —
namespaces array of object no —
unrecognizedImages array of string no Image repositories no add-on image matcher claims; sorted, deduplicated, at most 200.
unrecognizedImagesOmitted integer no Unrecognized image repositories the cap dropped.
provider string no Known values: eks, gke, aks, other. The managed Kubernetes service the collector inferred, or other when the cluster shows none of the three; absent when it could not tell (the nodes were unreadable and the server version names no provider). A name this build does not know is accepted and has no support calendar (a newer agent may learn more providers); a value that is not a name of at most 63 bytes of letters, digits, -, _ or . is refused (422).
crds array of object no CustomResourceDefinitions: versions, status.storedVersions, and the custom resources at a deprecated or unserved version.
volumePlugins array of object no The in-tree volume plugins (the volumes capability) the pods, PersistentVolumes and StorageClasses name, one entry per plugin by its field name in a pod's volumes or a PersistentVolume's spec (glusterfs, awsElasticBlockStore), a StorageClass counting for the plugin of its in-tree provisioner (kubernetes.io/rbd): plugin, count (the pods, PersistentVolumes and StorageClasses, or in files mode the pod templates, Pods, PersistentVolumes and StorageClasses, naming it), namespaces (that count per namespace; a bound PersistentVolume under its claim's, an unbound one and a StorageClass under the empty key), and objects (live, the PersistentVolumes and StorageClasses by name, never a pod; in files mode file, line, namespace and name; at most 100) and objectsOmitted. An agent that predates PersistentVolume and StorageClass reads sends pods only, and is still valid. At most 64 entries, each plugin once; a plugin must be a field name (at most 64 ASCII letters and digits, starting with a letter), no count, objectsOmitted or namespace count negative, namespace keys and object refs namespace and object names (422 otherwise). An agent drops an entry that would be refused, marking volumes partial. An inventory without the volumes capability, as older agents push, is not assessed for them.
addOnEvidenceAgeSeconds integer no How old, in seconds, the pod evidence the add-ons were detected from was when the agent reused its last full pod pass instead of listing every pod again (--pod-pass-every, --pod-pass-max-age). Absent when every pod was read. Not part of the snapshot's identity, like collectedAt: a push that differs only in it is a duplicate and is not stored, so the stored report keeps the age it was first pushed with, as it keeps that push's collectedAt (the evidence is as of collectedAt).
apiServerStartTime string (date-time) no process_start_time_seconds of the kube-apiserver whose /metrics deprecatedCalls were read from, in whole seconds: apiserver_requested_deprecated_apis counts requests since then. Absent when the scrape did not report it. Not part of the snapshot's identity, like collectedAt: a push that differs only in it is a duplicate. The server ignores one before 2014 or more than 10 minutes after collectedAt.

PushAccepted

Field Type Required Description
snapshotId integer (int64) yes —

PushDuplicate

Field Type Required Description
snapshotId integer (int64) yes The latest snapshot
duplicate true yes —

RenameRequest

Field Type Required Description
name string yes —

Registry

Field Type Required Description
addons array of AddOn yes —

Citations

Type: array of string (uri) or null.

AddOn

One add-on registry entry (registry/data/.yaml).

Field Type Required Description
schema_version integer yes —
id string yes —
display_name string yes —
matchers object yes —
support object yes —
cycles array of object no —
compat array of object no —
recommendation string no —
endoflife_product string no —