SDK changelogs: shipping one API in seven languages

An SDK family is one product wearing seven costumes. The API underneath moves once — a new endpoint, a renamed field, a retired auth scheme — and that single change has to surface in the Python package, the Node package, the Go module, the Java artifact, the Ruby gem, the PHP library, and the .NET assembly, each with its own version number, its own release rhythm, and its own changelog that somebody’s dependency bot will paste into a pull request. Most teams discover the problem the day a customer asks “which version of the Python SDK supports API version 2024-11?” and nobody can answer without reading git history. This guide is about the changelog architecture that answers that question in one click — and the writing habits that keep seven changelogs honest when one API moves.

Two layers: the contract and the bindings

The single most important decision is structural, not stylistic: keep one canonical API changelog, plus one changelog per SDK — and give every entry a home in exactly one of them.

  • The API changelog records changes to the contract: endpoints, fields, semantics, rate limits, auth, deprecations. It is language-neutral and it is the source of truth — everything a consumer could observe regardless of which SDK (or raw HTTP) they use. Everything in the API changelog guide applies to it verbatim.
  • Each SDK changelog records changes to the binding: new helper methods, renamed parameters, dropped runtimes, fixed retries, type corrections. Its reader is the person holding a dependabot PR for that package, and everything in the CLI & library guide applies to it — the 30-second merge decision, the verdict line, breaking changes first.

The rule that connects them: when an SDK release exists to expose an API change, the SDK entry links the API entry. “Added support for the Payouts API (API changelog: 2026-07-12)” tells the reader where the real story lives and keeps you from rewriting the same announcement seven times, slightly differently, with seven chances to introduce an error. The inverse link is optional; the forward link is not. And when an SDK release contains no API change, say so — “API surface unchanged; binding fixes only” is the single most merge-encouraging line an SDK changelog can carry.

Lockstep or independent versions — pick one and publish the map

There are two defensible versioning schemes for an SDK family, and an undefendable middle ground where most teams actually live.

  • Lockstep: every SDK ships the same version number, bumped together. The version names the API surface (“SDKs at 12.x speak API 2026-06”). Cross-language support answers become trivial. The cost: a breaking change in one language burns a major in all seven, so Go users see v13.0.0 whose Go-visible change is nothing — and per semver, a major that changes nothing for you is noise wearing an alarm bell.
  • Independent: each SDK follows its own semver, driven by what changed in that binding. Versions stay honest per package. The cost: version numbers stop encoding API coverage, so you owe your users a published mapping table — SDK version → API version supported — kept current in the docs and linked from every changelog entry that moves the mapping.

Either works. What does not work is independent versions without the mapping table, which is where “which Python SDK speaks 2024-11?” goes to die. If you cannot maintain the table, take lockstep’s noise cost — it is smaller than the support cost of an unanswerable question.

Generated SDKs: the changelog is still hand-written

If your SDKs are generated from an OpenAPI spec or protobuf definitions, the temptation is to let the generator write the changelog too: regenerate, diff, publish the diff. Resist it, for the same reason generated changelogs are drafts, not release notes: the diff knows what changed, never why or whether you care. A mechanical “regenerated against spec v2026-07-12” wall of added/removed method signatures forces every reader to do the triage you skipped. Generate the draft; then write the three lines that matter: what’s new, what breaks, what to do.

Two generator-specific traps deserve their own entries when they fire:

  • Generator upgrades are releases. Bumping the codegen tool can change method casing, nullability, error hierarchies, or async idioms across the whole surface — an enormous binding-breaking change with zero API change. That is a major version and a loud entry in the SDK changelog, with the API changelog silent, which is exactly right: the contract didn’t move, the costume did.
  • Spec corrections change behavior. Fixing a wrong type in the spec (“this field was documented as int, it’s actually a string”) generates a compile-breaking SDK change for a service that never changed. Write it as what it is — a correction — and link the docs correction so nobody hunts for a service-side change that never happened.

Parity: say which languages have it, every time

Features land in SDKs in waves — TypeScript on launch day, Python a week later, PHP eventually. Every team knows this; almost no changelog admits it. The reader of your launch entry has exactly one question — “can I use this from my language today?” — and silence makes them find out by upgrading, failing, and filing a ticket.

The honest pattern costs one line in the API changelog entry: “Available now in Node and Python; Go, Java, and Ruby to follow.” Then, as each SDK catches up, its own changelog entry announces arrival and links back. If a language is intentionally never getting the feature (streaming in a language whose HTTP stack fights it), say that once and stop apologizing. Parity honesty is the SDK-family version of the announce-three-times discipline: it trades a moment of looking slower for never looking evasive.

Per-language breaking changes live in per-language changelogs

Each binding has its own breaking surface that has nothing to do with the API:

  • Dropping a runtime — ending Python 3.8 or Node 16 support is a breaking change for that SDK only. It belongs in that SDK’s changelog as a major bump, never in the API changelog.
  • Idiom renames — aligning a method with language convention (snake_case, builder patterns, context-first arguments) breaks callers while changing nothing on the wire.
  • Error hierarchies and defaults — reshaping exception classes, changing retry/timeout defaults, or swapping the bundled HTTP client are the library-interface changes the library guide catalogs; they’re per-SDK entries with per-SDK majors.

Deprecations run the other direction: an API deprecation fans out into N SDK deprecation warnings, and the dates must agree. Coordinate them in absolute terms — one sunset date, stated identically everywhere, per your deprecation policy — because the person who reads only the Ruby changelog must see the same deadline as the person who reads only the API changelog.

An SDK entry that survives the bump PR

## 13.2.0 — 2026-07-29

**API surface:** adds Payouts API support
(API changelog: 2026-07-12 — link).

- **New:** client.payouts.create() / list() / cancel().
- **Fixed:** retries no longer double-fire on connection reset
  (affected 13.0.0–13.1.2).
- **Runtimes:** Python 3.9+ (unchanged).

Safe to bump from any 13.x. No action needed unless you
subclassed the transport (see migration note).

Every line earns its place: the API link kills duplicate explanation, the affected range serves the version-jumper, the runtime line answers the CI matrix question, and the verdict line closes the merge decision. That is the whole job.

Anti-patterns

  • Seven changelogs, no canon. Each SDK repo documents changes its own way and no language-neutral API changelog exists — so the contract’s history lives nowhere and every cross-language question becomes archaeology.
  • The regen wall. “Regenerated from spec” plus four hundred lines of signature diff. A draft published as a release note.
  • Lockstep ghost entries. Empty “no changes for this SDK” releases shipped to keep versions aligned, burying the releases that do carry changes. If you choose lockstep, at least write the one line saying why the version moved.
  • Parity silence. Launch entry announces the feature; five SDKs don’t have it; nothing says so anywhere.
  • “Misc improvements” bindings. The API changelog gets the real writing and every SDK entry says “updates.” The SDK entry is the one that gets read in the PR — it deserves the verdict line most.
  • Version soup. Independent versions, no mapping table, docs that say “use the latest.” Latest of seven things, compatible with which of twelve API versions?

Where Wakelog fits

Wakelog’s shape happens to match the two-layer architecture exactly: create one project for the canonical API changelog and one per SDK — each gets its own public page, RSS + JSON feeds, README badge, and permanent entry URLs, so an SDK entry can link the API entry it implements and a bump PR can embed either. CI posts on tag push per language (a matrix job works — the same pattern as a monorepo’s project-per-package setup), drafts let the codegen pipeline file the mechanical half while a human writes the verdict line, and the changelog.md export round-trips any of them back into its repo. The honest caveat: Wakelog won’t diff your OpenAPI spec or generate the SDKs — it hosts the story you write about them.

Start your free changelog   Next: API changelogs →

Related guides

Last updated 2026-07-29 · All guides