Stage 7 · Master
API Versioning and the Platform Contract
Versioning and Backward-Compatible Changes
Meridian's public API does not exist beyond /healthz yet, but the architecture already determines the version boundary: the gateway owns the external contract, and additive change must preserve the response envelopes defined in libs/platform/response and libs/platform/errors.
Gateway Owns the Public Version
The browser never calls identity-service, community-service, or billing-service directly. That architectural choice means the public API version belongs to apps/gateway, not to each internal service independently. Internal services may evolve their handlers and repositories behind the gateway, but the externally visible versioned path and envelope contract are owned at the single public entrypoint. That keeps version negotiation out of service-to-service traffic and concentrates compatibility guarantees where clients actually connect.
This lesson specifies versioning policy for the implementation phase. apps/gateway/internal/router/router.go currently registers only GET /healthz, so no public v1 route surface exists yet.
Platform Contract Is the Stable Shape
The most stable part of the future contract already compiles today: success responses use response.Envelope[T] with { data, meta?, error: null }, and failures use errors.APIError with { code, message, trace_id, details? }. Versioning policy should treat those envelopes as the outer contract that survives additive change. Adding fields inside data is often backward-compatible; removing or renaming the outer envelope is not.
Rules for Additive Change
- Adding optional fields under data is usually backward-compatible if existing fields keep their meaning and types.
- Adding new endpoints under the same major version is compatible if existing routes and envelope shapes remain stable.
- Adding new error codes is compatible only if old codes keep their semantics and clients can ignore unknown codes safely.
- Changing tenant slug resolution rules, authorization semantics, or pagination behavior may be breaking even when JSON shape stays similar.
Breaking Change Handling
| Change | Compatibility assessment | Handling |
|---|---|---|
| Add optional resident.phone to a response payload | Usually compatible | Ship in current gateway major version |
| Rename data to result in the outer envelope | Breaking | Requires a new public version and migration plan |
| Change invoice amount from integer cents to formatted string | Breaking | Version explicitly and keep the old representation available during transition |
apps/gateway/internal/router/router.gomodifyResponsibility: Mount versioned public route groups such as /v1/... while keeping /healthz outside the versioned API surface.
Why now: The gateway owns the client-visible contract boundary.
Connects to: Extends the current top-level ServeMux rather than creating a second public entrypoint.
apps/gateway/internal/contracts/v1createResponsibility: Hold the gateway's client-facing request and response DTOs for the first public version.
Why now: Versioned DTOs make compatibility review explicit and keep internal service changes behind the gateway boundary.
Connects to: Uses the shared response and error envelopes from libs/platform.
The Decision
Meridian versions its public API at the gateway boundary and treats the Platform Contract envelopes as stable outer shapes. Additive change is preferred, but compatibility is judged by semantics as well as JSON fields. Breaking change therefore requires an explicit gateway-level version transition, not a silent internal-service rewrite.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.