Semantic versioning for SDKs is fundamentally different from API versioning. While your API version tracks changes to HTTP contracts, your SDK version must reflect any modification that could break a developer's existing code, including type changes, method renames, or structural refactoring.
Most teams manually version their SDKs, which becomes unsustainable once you're shipping multiple languages. This guide covers how to identify breaking changes in generated code, automate semantic versioning across language ecosystems, coordinate multi-repository releases, and maintain the developer trust that comes from predictable, well-documented version bumps.
Semantic versioning for SDKs is not about your API's HTTP contract; it's about your user's code. A change is "breaking" if it forces a developer to change their code to upgrade, even if the underlying API is stable. Automating this process across multiple languages is critical for maintaining developer trust and is achievable with the right tooling and practices.
Identify breaking changes in an SDK
A breaking change in an SDK is any modification that could cause a consumer's existing code to fail at compile-time or runtime. This is a wider definition than a breaking API change, because it includes changes to the SDK's own code structure, types, and helper methods, not just the underlying HTTP requests and responses. Understanding this distinction is the first step to versioning your SDKs correctly.
Define SDK and API boundaries
The API contract and the SDK contract are two different things. Your API can remain perfectly stable while your SDK introduces a breaking change. This happens more often than you'd think.
For example, these are all breaking SDK changes, even if the API's JSON payload is untouched:
Renaming a generated type from
Transaction
toPaymentTransaction
.Changing a field's type from
string | null
to juststring
, removing its nullability. In languages like Java, making Java nullable fields backwards compatible requires special attention.Refactoring a method from
client.users.create()
toclient.users.add()
.Altering a pagination helper to return an async iterator instead of a simple list.
A robust SDK generation process will provide diagnostics that flag these kinds of potentially breaking changes before they are released.
Detect breaks in generated code
When you regenerate an SDK from an updated OpenAPI spec, the generator itself can introduce breaking changes. Modern tools are announcing the Stainless SDK generator capabilities to help detect these issues early.
Good SDK generators mitigate this by using a three-way merge process. This preserves any custom code you've added to the SDK while clearly highlighting conflicts between your customizations and newly generated code, forcing a manual review. This prevents accidental breaking changes from slipping through.
Automate semantic versioning for SDKs
Once you're shipping SDKs in more than one language, manual versioning and changelog management becomes unsustainable. It's tedious, error-prone, and a huge time sink. Automation is the only scalable solution for maintaining release hygiene and developer trust.
Compare release tools
Two popular tools for release automation are semantic-release
and release-please
.
semantic-release: A powerful tool that analyzes commits on your main branch, determines the version bump, and immediately publishes the package. It's great for single-package repositories where direct pushes to main trigger releases.
release-please: This tool works by opening pull requests that propose a version bump and a generated changelog. The release only happens when you merge the PR. This PR-based workflow is ideal for SDKs, as it gives you a chance to review, and it works beautifully for managing releases across many repositories at once.
We find the pull request model of release-please
provides a crucial review step that is perfect for managing a suite of SDKs.
Configure continuous releases
The automated release workflow is straightforward and powerful. It starts with developers using a standardized commit message format.
The flow looks like this:
A developer pushes a commit with a message like
feat(users): add support for user profiles
orfix: correct pagination logic
.An automation tool, like
release-please
, runs in your CI pipeline. It parses the commit history since the last release.The tool determines the next version (e.g., a
feat
commit triggers a minor bump) and opens a pull request with the proposed version and an updatedCHANGELOG.md
.Your team reviews the release PR. Once merged, the CI pipeline tags the release and publishes the new version to the package manager.
Use pre-release versions effectively
Pre-release versions are essential for communicating the stability of your SDKs. Using tags like -alpha
and -beta
manages user expectations, especially when an SDK is new or undergoing significant changes.
Start in alpha for new SDKs
Your very first release should be an alpha version, like 0.1.0-alpha.1
. This signals to developers that the SDK is experimental and its public interface is not yet stable. Users understand they should pin the version precisely and expect potential breaking changes even between alpha releases.
Graduate to stable versions
Moving from a pre-release to a stable 1.0.0
version should be a deliberate decision. It tells your users that you consider the SDK's public surface to be stable and will follow semantic versioning rules strictly.
Before you move to 1.0.0
, you should have a simple checklist:
Are there any generator errors or diagnostics?
Have all warnings been reviewed and acknowledged?
Is the
README.md
polished and are the examples working? Have you started to integrate SDK snippets with your API docs?
Only after meeting this quality bar should you release a 1.0.0
version.
Keep multi-language SDKs in sync
Managing five or more SDK repositories is a significant coordination challenge. Deciding on a versioning strategy upfront will save you headaches down the line.
Choose synchronized or independent versions
You have two main options for versioning your suite of SDKs.
Synchronized: All SDKs (TypeScript, Python, Go, etc.) share the same version number. If you release
v1.5.0
, all SDKs get av1.5.0
release. This is simple to communicate and makes documentation easier.Independent: Each SDK has its own version. The Python SDK could be
v2.1.0
while the Go SDK isv1.8.3
. This allows for language-specific fixes without requiring a release for every other language.
For most teams, starting with synchronized versioning is the simpler and better approach.
Coordinate cross-repo releases
A modern release process can automate cross-repository coordination. The ideal flow uses a central next
branch to stage upcoming changes for all SDKs, similar to how you can edit configs and OpenAPI specs with branches before merging to main.
When a change is made, it's pushed to the next
branch in every SDK repository. This automatically generates a "Release PR" for each language, all proposing the same version bump. This gives you a single, unified view of the upcoming release across your entire SDK ecosystem.
Follow proven SDK versioning practices
Adopting a few key practices will dramatically improve the quality and reliability of your SDK releases. These habits form the foundation of a trustworthy, automated release pipeline.
Adopt conventional commits everywhere
Conventional Commits are a specification for commit messages that provides a simple, human-readable structure that machines can also parse. This is the engine that drives automated versioning and changelog generation.
Commit Type | Version Bump | Description |
---|---|---|
| Minor | A new feature for the user. |
| Patch | A bug fix for the user. |
| None | Changes to documentation only. |
| None | Code changes that neither fix a bug nor add a feature. |
| Major | A footer in any commit type that signals a breaking change. |
Ship changelogs users trust
A great changelog is clear, accurate, and easy to find. When you automate your releases based on conventional commits, you get a high-quality CHANGELOG.md
file for free. Because it's generated directly from your commit history, developers can trust that it's a complete and accurate record of what's changed.
Support older versions responsibly
You can't support every old version of your SDK forever. A common and reasonable policy is to provide bug fixes (patch releases) only for the latest major version. Critical security fixes may be backported to a previous major version for a limited time. This strategy is easy to manage with a main
branch for the current release and a separate maintenance branch for the older version.
Frequently asked questions about SDK versioning
Here are answers to some common questions engineers have when setting up semantic versioning for their SDKs.
Should SDK and API versions match?
No, they should track different things. The SDK version signals breaking changes in the client-side code, while the API version signals breaking changes in the HTTP contract.
When do dependency updates change the SDK version?
A dependency update that fixes a security vulnerability should trigger a patch release. If a dependency introduces its own breaking change that you expose through your SDK, that requires a major version bump for your SDK.
How do I version SDK-only helpers like retries?
Treat them as part of the SDK's public surface. Adding a new helper method is a feature (feat
), resulting in a minor bump. Changing the signature of an existing helper is a BREAKING CHANGE
, requiring a major bump.
What version should I start with for a brand-new SDK?
A great starting point is 0.1.0-alpha.1
. This clearly communicates that the SDK is new and its API is not yet stable, encouraging users to be cautious with upgrades.
How does Stainless update versions for me?
When you make changes, a pull request is automatically created with a suggested version in the title, based on your commits. You can edit this title to override the version, and merging the PR automatically tags the release and publishes it for you.
Ready to automate your SDK releases? Get started for free.