DEV Community

Jeff
Jeff

Posted on Originally published at powerduck.com

Generating an API Changelog Automatically from Spec Diffs

Quick answer: A reliable API changelog is generated by diffing two versions of the OpenAPI spec and classifying each change as breaking, non-breaking, or a deprecation, rather than maintained by hand where entries are forgotten or mislabeled. The pipeline compares the released spec against the previous one, maps each removed or changed element to a readable entry, and blocks a release if a breaking change is detected without a version bump, so the changelog reflects exactly what the contract did. The trade-off is that the spec must be complete and accurate, because a diff tool can only report what the descriptions contain; prose rationale still has to be added by a reviewer. If you treat the changelog as a separate document updated at release time, it will inevitably lag the API, and consumers will learn about a removed field from a failed integration rather than from a dated entry.

Consumers of an API need to know what changed and whether the change affects them, and a hand-written changelog is the familiar answer, but it sits outside the artifact that actually defines the API. The spec is the source of truth; every endpoint, field, and enum is declared there, and every change to the contract is a change to a structured document. Comparing two versions of that document yields a precise list of differences, which is the raw material for a changelog. The added value of a tool is classification: removing a response field is breaking, adding an optional field is non-breaking, and marking an endpoint deprecated is a distinct signal, and getting those labels right matters because they tell consumers whether they need to act.

How spec changes map to changelog entries

The classification is what turns a raw diff into something a consumer can act on.

Spec diff Classification Consumer impact
Removed endpoint or field Breaking Must migrate
Required field added Breaking Requests may fail
Optional field added Non-breaking None
Field deprecated Deprecation Plan ahead
Description clarified Non-breaking None

The required field added row is easy to mislabel; it looks like an addition but is breaking for requests because clients now have to supply a value they did not previously need.

Diff the released spec in the pipeline

At release, compare the current spec against the last tagged version and generate the changelog entries, failing the build if a breaking change appears without the required version increment.

Running the diff against a tagged, immutable previous version rather than a local copy keeps the comparison reproducible, so the same release always produces the same changelog regardless of who runs the pipeline.

Add human rationale without losing accuracy

A diff explains what changed but not why, so the generated entries should be augmented with a short reviewer-written note and a migration pointer where one exists. Keep the generated and authored sections clearly separated so the tool remains the authority on the factual list.

Because the changelog is derived from the contract, an incomplete spec is the main risk. If fields are undocumented or responses are loosely described, a real change can ship without a corresponding entry, which is why spec completeness and linting should be enforced in the same pipeline that generates the changelog.

Publish the changelog with the release

Make the generated changelog part of the release artifact and expose it alongside the docs, dated by the release tag, so consumers can subscribe to a stable location. The contract version, the changelog entry, and the deployed API should all be produced in one pipeline, removing the possibility that a release goes out without its documented changes.

The practical rule is to generate the changelog from spec diffs and classify each change mechanically, then let a reviewer add the why and a migration pointer while the tool keeps the authoritative what. A manually maintained changelog depends on memory and mislabels subtle breaking changes like a newly required field. Keep the spec complete, diff against an immutable tag, and block a release that breaks the contract without a version bump, and the changelog becomes a trustworthy, dated record of the actual evolution of the API.

Frequently asked questions

Why generate a changelog instead of writing one by hand?

The spec already records every change structurally. Diffing versions gives a complete, reproducible list and mechanical classification, while a hand-written entry can be forgotten or mislabeled.

What makes an added field breaking?

Adding a required field is breaking because existing requests now fail without it. An optional field is non-breaking, which is why the classifier distinguishes the two.

Can a changelog explain why a change was made?

The tool reports the factual what; a reviewer adds a short note and migration pointer in a clearly separated authored section, keeping the generated list authoritative.

What would make a generated changelog miss a change?

An incomplete or loosely described spec. If a field is undocumented, the diff cannot see it, so enforce spec completeness and linting in the same pipeline.

Which baseline should the diff compare against?

An immutable tagged release rather than a local copy, so the comparison is reproducible and the same release always yields the same changelog.

This is part of the Powerduck vs Redocly comparison — start there for the full picture of API governance and documentation choices.

Top comments (0)