Thesis: Clients and CI pin to a URI major (/v1/…). OpenAPI info.version should name that same surface. A bare /health alias can stay for humans; the contract for install and retainer integrations is the versioned path.
What we shipped first
We published a headless API for install and retainer with OpenAPI 1.3.0 and a /v1 URI major. Unversioned health still answered, so a green check on /health did not prove a client was calling the versioned contract. Swagger and docs behind auth are for operators, not a substitute for the published OpenAPI document clients consume.
The working shape
Same handlers under /v1/… as the stable contract. Keep OpenAPI info.version (for example 1.3.0) honest about the URI major. Prefer HATEOAS links that already include /v1. Treat unversioned routes as optional convenience. Examples use placeholders only (https://api.example.com/v1/health).
The snippet below pins a client at /v1/health and keeps bare /health as operator convenience.
// Pin clients to the URI major. OpenAPI info.version should match that surface.
const API = "https://api.example.com";
async function healthUnversioned() {
// Convenience for operators — not the integration contract.
return fetch(`${API}/health`);
}
async function healthContract() {
// What install + retainer clients should call and pin.
return fetch(`${API}/v1/health`);
}
// OpenAPI info.version example: "1.3.0" (URI major /v1 + semver patch surface)
Checklist
- Decide the URI major before the first external pin.
- Align OpenAPI info.version with that major.
- Document which routes are non-contract aliases.
- Smoke both /health and /v1/health.
Related: branded API hostnames. How we deliver: methodology · readiness.
Engineering commentary only — not audit, legal, or certification advice.