Versioning and compatibility
How the Voult API and the @voult/* packages are versioned, which versions work together, and how things are deprecated and removed.
The API
The API has one version, semver, shown in three places that always agree:
GET /api/meta→{ "version": "1.1.0", "minSdkVersion": "0.1.3", ... }- the
X-Voult-API-Versionheader on every/api/*response, errors included (since 1.1.0) versioninpackage.jsonanddocs/openapi.yaml(a test keeps them equal)
| Change | Bump | Examples |
|---|---|---|
| Breaking | major | Removing or renaming a route, field or error code; changing a field's type or meaning; making an optional parameter required; rejecting input that used to be accepted; changing a status code or what a route needs to authenticate |
| Compatible addition | minor | A new route, optional parameter, response field, header or error code (for a new failure case); a compatible behaviour change, such as a shorter token lifetime that clients already handle by refreshing |
| Fix | patch | A bug fix that changes no documented contract |
To stay compatible with minor releases, clients must ignore response fields they don't know and treat an unknown error code as a generic failure for its HTTP status.
Every API change is recorded in the root CHANGELOG.md. CI fails a change to controllers/, routes/, services/ or models/ that has no entry.
The packages
@voult/sdk, @voult/core, @voult/express, @voult/react, @voult/cli (and later @voult/next, create-voult-app) each have their own version and CHANGELOG in voult-sdk, and are released from CI with provenance (RELEASING.md).
They are 0.x until launch:
- Minor (
0.2.0 → 0.3.0) may contain breaking changes. Each one is marked Breaking in the package's CHANGELOG with what to change.npm installwrites^0.3.0, which never crosses a minor in 0.x, so you only get breaking changes when you choose to upgrade. - Patch (
0.3.0 → 0.3.1) is fixes and compatible additions, never a breaking change. - Prereleases (
0.3.0-beta.0) are published under thenextdist-tag (npm install @voult/express@next), never aslatest. - Only the latest minor of each package gets fixes.
From 1.0 (at launch) the packages follow plain semver: breaking changes only in a major.
Compatibility
| Voult API | @voult/sdk |
@voult/express |
@voult/core |
@voult/react |
@voult/cli |
|---|---|---|---|---|---|
| 1.0 – 1.2 (current) | 0.2.x (0.1.3+ still works for password, MFA, magic link and passkeys; hosted OAuth and linking need 0.2.0) | 0.2.x, 0.3.x | 0.1.x, 0.2.x | 0.1.x, 0.2.x | 0.2.x |
@voult/expressand@voult/coreneed@voult/sdk0.2.0+ (peer dependency);@voult/express0.3.0-beta.0 runs on@voult/core0.1, 0.3.0 on 0.2.@voult/core0.2 /@voult/express0.3.0 use two API 1.2 additions:isCurrenton sessions (on 1.0–1.1 it's missing, so revoking your own session doesn't sign that browser out) and MFA on magic-link sign-in (on 1.0–1.1 a magic link signs MFA users straight in).@voult/reacttalks to your server (@voult/express, later@voult/next), not to the API, so it follows the server package rather than the API.minSdkVersioninGET /api/metais the oldest SDK the running API supports.npx voult doctorfails when your project's SDK is older.- When the API ships a new major, this table gains a row, and the SDK release that supports it says so in its CHANGELOG.
Security fixes
A security fix can change behaviour in a minor or patch release, even when that rejects requests a previous version accepted. Waiting for a major (or a 90-day deprecation) would leave the hole open. Such changes are marked Security in the CHANGELOG, with what to do if they affect you.
Deprecation
Nothing a client can rely on disappears without warning. A deprecated route, field, parameter, header, env variable or package export:
- keeps working for at least 2 minor releases of the package that carries it (for an API change: of
@voult/sdk) and at least 90 days, whichever is later, after it is announced; - is announced in both CHANGELOGs: the root CHANGELOG.md for the API and the affected package's CHANGELOG, with the replacement and the earliest removal date;
- is listed below until it is removed;
- tells the caller at runtime where it can: API routes send a
Deprecationheader (with aSunsetdate once removal is scheduled), packages log a one-time warning, andvoult doctorflags what it can see.
Removing it afterwards is a breaking change: an API major, or a package minor while in 0.x (marked Breaking).
Currently deprecated
| What | Replacement | Deprecated since | Removed no earlier than |
|---|---|---|---|
redirectUrl in linkOAuthProvider()'s result (@voult/sdk) |
authUrl |
@voult/sdk 0.2.0 (2026-09-26) |
@voult/sdk 0.4.0 and 2026-12-25 |
Env names CLIENT_ID, CLIENT_SECRET, BASE_URL, SESSION_SECRET, APP_URL (@voult/express, warns once at startup) |
VOULT_CLIENT_ID, VOULT_CLIENT_SECRET, VOULT_BASE_URL, VOULT_SESSION_SECRET, VOULT_APP_URL |
@voult/express 0.1.0 (2026-09-22) |
@voult/express 0.4.0 and 2026-12-21 |
Error codes
Error codes are part of the API contract:
- A code is never renamed and never reused for a different meaning. A new failure case gets a new code.
- New codes can appear in any minor release. Handle unknown codes by HTTP status.
- Every code the API can return is in ERRORS.md; a test fails the build when one is missing. A code that stops being returned stays in the catalog, marked with the version that stopped using it.
How the docs track versions
- The docs in this repository describe the API on
main, which is what staging runs. Anything added after 1.0 is marked with the version that introduced it ("since 1.1.0"), so you can tell whether the API you talk to has it: compare withX-Voult-API-Version. docs/openapi.yamlcarries the API version and is bumped withpackage.json(a test fails if they differ).- The CHANGELOGs are the history: what changed in each version, and what to do about it.
- Package READMEs ship inside each package, so npm shows the README for the version you install.