Versioning policy
SemVer bump rules, deprecation policy, per-service current versions, deprecation labels in the OpenAPI spec.
How the three YieldFabric public API specs version, what triggers a major / minor / patch bump, and what guarantees a consumer can rely on.
Per-service versions, one platform
Each service has an independent info.version in its OpenAPI spec
and its own CHANGELOG. There is no platform-wide API version —
clients pin to the per-service versions they integrate against.
0.3.00.4.00.12.1The Current version column above is hand-maintained and can drift
from a live deployment. To learn the version a running service
actually serves, probe its GET /version endpoint — see
Discovering the served version
below.
All three use SemVer 2.0: MAJOR.MINOR.PATCH.
Discovering the served version
A spec version in a doc or a CHANGELOG tells you what the code in the
repo claims. It does not tell you what a given deployment is
actually running — those can diverge when a deploy lags, rolls back,
or pins an older image. Every public service therefore exposes an
unauthenticated GET /version probe so the served contract is
self-describing.
/version is a sibling of /health: no JWT, no body, no business
state. It returns the running build's identity:
{
"service": "yieldfabric-agents",
"api_version": "0.12.1",
"git_sha": "a1b2c3d4e5f6",
"built_at": "2026-06-14T09:31:02Z"
}
serviceyieldfabric-auth / yieldfabric-payments / yieldfabric-agents).api_versioninfo.version, baked at compile time from its own docs/api/openapi.yaml — see below.git_shagit rev-parse, else "unknown").built_atapi_version is baked from the spec at compile time
The value the binary reports is not a hand-edited string and not
CARGO_PKG_VERSION. Each service has a build.rs that calls the
shared crate
(bake()), which reads info.version straight out of that crate's
docs/api/openapi.yaml and emits it as a SERVICE_API_VERSION
compile-time env. The GET /version handler returns it via
yieldfabric_build_meta::VersionResponse. Because the spec is the
single source, a deployment cannot advertise a version it doesn't
implement — if the binary serves 0.12.1, it was built against the
0.12.1 spec.
The drift this closes
Before /version, the only way to tell which API version a deployment
served was to probe individual routes for feature presence. A consumer
once hit a deployment that looked healthy but silently served an
older API (e.g. 0.8.0 where the code in the repo was 0.11.0) —
discovered only when a feature that the newer spec documents (model
selection on the OpenAI-compatible /v1 surface) was simply absent at
runtime. One GET /version would have surfaced the mismatch
immediately. The probe makes "is this deployment on the version I'm
integrating against?" a single request instead of a route-archaeology
exercise.
Probe each service
# auth (auth.yieldfabric.com)
curl https://auth.yieldfabric.com/version
# → {"service":"yieldfabric-auth","api_version":"0.3.0","git_sha":"…","built_at":"…"}
# payments (pay.test.yieldfabric.com)
curl https://pay.test.yieldfabric.com/version
# → {"service":"yieldfabric-payments","api_version":"0.4.0","git_sha":"…","built_at":"…"}
# agents (agents.yieldfabric.com)
curl https://agents.yieldfabric.com/version
# → {"service":"yieldfabric-agents","api_version":"0.12.1","git_sha":"…","built_at":"…"}
No Authorization header — /version is reachable before a consumer
holds any credential, exactly so it can be checked at integration time
or in a health/readiness gate.
Bump rules
Patch (X.Y.Z → X.Y.Z+1)
Non-breaking corrections. No SDK regeneration required, no client changes required.
- Typo fix in a description
- Clarified prose
- Fixed example that didn't compile
- Regenerated code sample
- Updated error-catalogue prose (without adding/removing codes)
- Drift fix where the doc said X and the code did Y — bringing doc in line with code that hasn't changed
Minor (X.Y.Z → X.Y+1.0)
Additive, backward-compatible. SDK regeneration recommended but existing client code keeps working.
- New operation / endpoint
- New optional input field
- New optional response field
- New enum value (consumers should switch on known values and accept-but-ignore unknowns — see "Forward compatibility" below)
- New error code
- New example
- New guide
Major (X.Y.Z → X+1.0.0)
Breaking. Existing client code may fail. Requires migration.
- Renamed operation
- Removed operation, field, or enum value
- Required field became required (was optional)
- Optional field became required-in-some-contexts
- Type narrowing (e.g.
string→enum) - Auth requirement changed on an endpoint
- HTTP status code changed for an existing case
- Error code removed or renamed
Pre-1.0 caveat
All three services are currently pre-1.0 (0.x.y). SemVer
allows breaking changes on minor bumps below 1.0, but we don't do
that — the three services follow strict bump rules even pre-1.0
so consumers can rely on the contract.
The 0.x line will turn over to 1.0 when:
- The spec is considered stable for external consumers.
- SDKs are generated and shipped.
- The deprecation policy below has been exercised at least once (so we know the playbook works).
Cross-service version compatibility
The services are independently versioned but the platform deploys them together. When one public surface changes in a way another YF surface depends on, YieldFabric handles the rollout order:
- Producer ships a minor version exposing the new shape alongside the old.
- All consumers update to use the new shape.
- Producer ships a major version removing the old shape.
In practice this means breaking cross-surface changes need a deprecation cycle (see below).
Deprecation policy
When a field, operation, or error code is removed in a major bump, it follows this lifecycle:
deprecated → removed
↑ ↑
announce one minor version cycle later AT MINIMUM
The minimum deprecation window is one minor version cycle (typically 2-4 weeks for active services).
How a deprecation is announced:
- Spec annotation. OpenAPI 3.1 supports
deprecated: trueon operations anddeprecated: truein schemas. Both are honoured. - CHANGELOG entry under the minor version that adds the replacement, explicitly listing the deprecated thing and its replacement.
description:prose explaining the migration path.
Example:
/auth/old-endpoint:
post:
operationId: oldOperation
deprecated: true
summary: (DEPRECATED) Old shape — use POST /auth/new-endpoint instead.
description: |
Scheduled for removal in v1.0.0. Migrate to
`POST /auth/new-endpoint` (same input shape, returns the
replacement type).
Forward compatibility — what consumers should assume
Consumers SHOULD write code that survives minor bumps:
- Treat enums as open-ended. When you receive an enum value you don't recognise, accept-and-ignore (or surface as "unknown" in UI), don't throw. New enum values arrive on minor bumps.
- Don't deserialise into strict types that fail on unknown
fields. Use lenient deserializers (
#[serde(default)]on Rust,JsonSerializerwithoutdisallowUnknownPropertiesin TS, etc.). - Switch on error
code, not errormessage. Messages are free-form and may shift between releases. Codes are the stable contract. - Respect
Retry-Afterheaders. If introduced, they're authoritative even if not previously specified.
Consumers MAY assume:
- HTTP status codes don't change for existing error cases.
requiredfields stay required.- Operation IDs don't change.
Pinning recommendations
How to pin per audience:
^0.2); auto-take patches; review minors before deployingIn the JS workspace, OpenAPI specs aren't versioned as npm packages today. If/when SDKs ship, they'll follow standard npm SemVer.
Change logs as the canonical record
Each service's CHANGELOG.md is the authoritative version log.
Format:
## [X.Y.Z] — YYYY-MM-DD
### Added (minor)
- New endpoint / field / enum value with a 1-line description.
### Changed (minor — additive) or (major — breaking)
- What changed, why, migration path if breaking.
### Fixed (patch)
- Drift between doc and code; typo; example fix.
### Deprecated
- Things on the path to removal in the next major, with target version.
### Removed (major)
- Things gone; what replaces them.
Entries always lead with the change type. Major bumps surface the breaking changes at the top of the file with a "Breaking changes" header.
What's NOT versioned
- Repo-root
README.mdandCLAUDE.md. Internal docs; versioned implicitly by git history. docs/architecture/trees. Internal; same.docs/getting-started/. Tutorial content; live with the spec, not separately versioned.- Webhook payloads for outbound webhooks (when they exist).
Each webhook event type is versioned via an
event_versionfield in the payload, separately from the spec version.
Out of scope
- API gateway version (Apollo Router config). Operator concern, not consumer-facing.
- Database schema versions. Internal; never exposed to API consumers.
- Internal library versions. Workspace versioning, not API versioning.
See also
docs/getting-started/cross-service-walkthrough.md— how the services compose at the consumer level.- Per-service
CHANGELOG.mdfiles in each service'sdocs/api/.