Skip to content

0012: The JSON API is defined by an OpenAPI document

Date: 2026-10-07 · Status: accepted

Context

The design lists the /api/v1 endpoints but does not say how they are specified. The console handlers, the CLI and the tests each had their own copy of the request and response shapes, written by hand. Nothing checked that they matched, and input checks were spread over the handlers.

Decision

  • One source of truth. api/openapi/v1.yaml (OpenAPI 3.0) describes every /api/v1 operation: parameters, request bodies, responses and errors. It also holds each operation's minimum role in x-keeper-role. The server serves the document at GET /api/v1/openapi.yaml.
  • Generated code. go generate ./internal/apiv1 (oapi-codegen, pinned as a Go tool in go.mod) builds internal/apiv1: the DTOs, a typed client and the embedded document.
  • The console handlers return these DTOs.
  • The CLI uses the generated client.
  • Storage formats (manifest, inventory, tags, previews), audit events and principals are not copied. The document describes them and maps them to the existing Go types with x-go-type.
  • make lint (hack/check-generated.sh) fails when the generated code is out of date.
  • Request validation. Every API request passes through kin-openapi's openapi3filter after authentication and before its handler. A request that does not match the document gets 400 with the failing parameter or body field. An unauthenticated request still gets 401.
  • Request bodies reject unknown fields (additionalProperties: false).
  • Names, durations, enums and limits are checked by their patterns and bounds.
  • Roles. Routes are registered from a table. Each route takes its role from the document and fails start-up if it is missing, so routes and documented roles cannot drift.
  • Response validation in tests. With KEEPER_API_VALIDATE_RESPONSES=true (or Options.ValidateResponses), every response is checked against the document, and a mismatch becomes a 500. The unit, CLI and UI tests and the e2e harness turn it on. Production does not, because it buffers each response. /events (SSE) is not checked.

Consequences

An API change starts in api/openapi/v1.yaml, then make generate. Third-party clients can be generated from the served document. Validation adds a route lookup and a schema check per request, which is well inside the console's latency budget.