The question "what version is my SDK?" has two answers most teams conflate. The SDK's npm version is a package lifecycle artifact — it controls which tarball `npm install` pulls. The API's version is a contract artifact — it tells a consumer what shape to expect over the wire. Whether those two numbers should track each other, and how, is a decision most teams make implicitly by accident.
The result of accidental strategy is familiar: an SDK whose `1.4.3` has no relationship to any version the server returns, an `info.version` in the OpenAPI document that never changes, and a release note pipeline that shrugs at both. When a breaking change lands, nobody knows whether to bump to 2.0.0 or 1.5.0, and the decision is made by whoever ships first.
This guide walks through the strategies in the wild, what each one buys you, what it costs, and what "versioning correctly" actually looks like when you generate the SDK from an OpenAPI document.
API version and SDK version are different things
An API version is a property of the contract. It identifies the shape of requests and responses. Clients read it to decide which endpoint path to hit, which feature flags to expect, which error formats to accept.
An SDK version is a property of the distributed artifact. It identifies the tarball installed by `npm`. Consumers read it to decide whether to upgrade, whether a change is breaking, whether to pin.
They are not the same number and do not need to be. A team can ship SDK `1.7.2` on top of API `v3`, and SDK `2.0.0` can be an SDK-side breaking change on the same API `v3`. The relationship is a choice.
Strategies in the wild
Semver on the SDK, decoupled from the API. The SDK bumps when the SDK breaks — typically on generator upgrades or intentional API-surface refactors. API bumps are independent and signalled only by a new endpoint path (`/v2/…`) or header. This works well for SDKs that wrap a slow-moving, URL-versioned API. Breaking changes stay rare.
SDK version tracks the API's `info.version`. Whatever the OpenAPI document says `info.version` is, that's the npm version. One source of truth, no confusion — and zero work on the SDK side. The entire versioning discipline lives in the spec. This is the cleanest setup when the API is already versioned seriously (bumps gated through review, release notes attached).
Calver on both. `2026.4.23`-style. Useful for APIs that release continuously with no backwards-compatibility promise (internal-only SDKs, early-stage products). Loses the "this is safe to upgrade" signal semver gives you; gains a cleaner incident timeline.
Stable / beta channels. The SDK ships two tags (`@latest` and `@next`). The API lives at a single endpoint or two parallel endpoints. Consumers opt in to beta. Expensive to maintain — you're running two pipelines — but the only sane model when the API ships breaking changes fast and the SDK consumers can't afford to catch them live.
The trap: diff-based automatic semver
Every few months someone proposes a tool that bumps the SDK version automatically by diffing the OpenAPI document. Additive diff → minor. Breaking diff → major. No humans required.
Diff-based bumping is wrong about a third of the time in practice. Renaming a field reads as "removed + added" — a breaking change diff — but the semantic change might be zero (the backend accepted both names for a week as a compatibility shim). Making an optional field required reads as "no shape change" on naive differs — the property still exists — but it's a breaking change for every caller omitting it. Tightening an enum (removing a variant) reads as breaking but might be a no-op if no real data used that variant.
The honest tools therefore don't auto-bump. They surface the diff, classify it with best effort, and let a human decide. The human is the spec owner — the person who actually knows whether the rename is a shim or a break.
What we recommend when you generate from OpenAPI
If you're generating the SDK from an OpenAPI document, use the SDK-tracks-`info.version` strategy unless you have a specific reason not to. One number, one discipline, one release note trail. The spec author owns the bump — which is the right shape: they know what they changed.
If your API doesn't currently take `info.version` seriously, make it take `info.version` seriously before you pick a different strategy. An SDK whose version isn't a real release number is going to be confusing no matter how you bump it.
If you need a stable/beta split, run two SDK Factory apps pointed at two OpenAPI URLs (one stable, one next) publishing to different dist-tags or different package names. You keep the "one version per SDK" discipline at the app level and let the channel split happen at the publish level.
# Treat info.version as the SDK's npm version. Bump it in the same
# PR that introduces a breaking change — the SDK rebuild picks it up
# automatically the next time the schema is polled.
openapi: 3.1.0
info:
title: Acme API
version: 1.7.2 # ← this becomes the published npm version
servers:
- url: https://api.acme.example/v1Communicating breaking changes
A version bump is not a release note. A semver major says "this is breaking" but not "here is how to migrate" — and the second sentence is the one consumers actually need.
The minimal discipline that works: a `CHANGELOG.md` at a stable URL, one entry per published version, with a plain-English "what changed" block. If the SDK is generated, that changelog is generated by the human who bumps `info.version` — there's no tool that can infer intent from a schema diff, and tools that try produce noisy text nobody reads.
For meaningful breakages, migration guides with code samples beat a one-paragraph note. These live outside the OpenAPI document — in whatever documentation surface you already run — and should be linked from the release notes directly.