ADR-0015 — URI-versioned public REST API; proto-evolution rules internally
- Status: Accepted
- Date: 2026-06-11
- Related: 03 FR-H / NFR-9, 02 NG4, ADR-0003
Context
BitVault exposes a public REST API consumed by the web app, the Go CLI, third parties, and a future React Native app (NG4). The mobile app cannot ship until the public API is stable and versioned (NG4). Internally, gRPC/proto contracts between modules evolve fast (ADR-0003). We need a versioning policy that gives external consumers stability while letting internal contracts evolve cheaply.
Decision
- Public REST API: URI versioning —
/v1/.... A major version is a stable contract; breaking changes require/v2and a documented deprecation window with overlap. - Backwards-compatible evolution within a major version: additive only (new fields/endpoints optional; never remove/repurpose fields; never tighten validation in a breaking way). OpenAPI (generated, ADR-0003) is the published contract.
- Internal gRPC/proto: governed by buf breaking-change detection in CI. Evolve via field additions and reserved tags; never reuse field numbers. Internal contracts may move faster than the public API because all consumers are in-repo (ADR-0002) and updated atomically.
- Errors: a single, stable error taxonomy (
pkg/apperr) mapped consistently to HTTP status + machine-readable codes, versioned with the API. - The public API freeze is the gate for the mobile app (NG4): RN work starts
only once
/v1is declared stable.
Consequences
Positive
- External consumers (CLI, third parties, future mobile) get a stable target; internal teams keep velocity on proto.
- buf CI prevents accidental internal breaking changes from shipping; OpenAPI keeps the public contract honest and generated (no drift, R8/ADR-0003).
- Clear, enforced gate for when mobile can begin (NG4) avoids building a client on a moving target.
Negative / costs
- Supporting overlapping major versions (when
/v2arrives) is real maintenance; mitigated by additive-only discipline keeping majors rare. - Generated-client coupling means a proto change must regenerate and update in-repo consumers in the same PR (a monorepo benefit, but a discipline).
- Deprecation windows require communication and tooling (sunset headers, docs).
Alternatives considered
- Header/media-type versioning: rejected as primary — less discoverable for the broad third-party/CLI audience than a path; URI versioning is the boring, well-understood choice.
- No versioning / “always latest”: rejected — unacceptable for a public API with external + mobile consumers (NG4).
- Version every internal gRPC call like the public API: rejected — unnecessary ceremony for in-repo, atomically-updated consumers; buf breaking-checks suffice.