A client that treats an unknown JSON field as an error can fail on Tuesday after your “backward-compatible” response adds archived: false. That release may deserve a minor bump only if the API contract says clients must tolerate that field.
Compatibility alone doesn’t decide the bump
Semantic Versioning gives stable public APIs two different kinds of compatible release: a patch for backward-compatible bug fixes and a minor for new, backward-compatible functionality. For a version such as 2.4.1, that means a bug-fix release might become 2.4.2, while a feature release might become 2.5.0.
Both releases can leave existing integrations working. The distinction is what a user receives: a patch corrects behavior that was already promised, while a minor release gives users a new capability they can choose to use. If a team asks only, “Will old code still run?”, it misses this difference and ends up with inconsistent release notes and version histories.
Start by identifying the public contract—not just the function signature. For an HTTP API, that can include status codes, response fields, validation rules, ordering, and documented side effects. For a library, it can include return values, exceptions, and supported input types.
Apply the bug-fix test
Ask: Does this change make existing documented behavior work as promised? If yes, and existing clients remain compatible, it is usually a patch. A “bug” is not merely an implementation detail the team dislikes; it is a difference between actual behavior and the public contract.
For example, suppose a documented endpoint promises results ordered by creation time, but sometimes returns them in an arbitrary order. Making the endpoint reliably use the documented order is a bug fix. So is correcting a calculation that returns the wrong total for a supported input, provided the correction doesn’t invalidate behavior that users were told to expect.
By contrast, changing a documented default from newest-first to oldest-first is not safely classified as a patch just because the team considers the old default a mistake. Clients may depend on the observed order. If the behavior was documented—or is a widely relied-on part of the public API—changing it can be breaking and may require a major release, or a transition plan.
Use a minor for a new capability
Ask: Can a user do something new, while clients written against the old contract continue to work? If yes, the change is usually a minor release. A new optional query parameter such as ?status=active is a typical example when requests without it continue to behave as before.
Other examples include adding a new endpoint, adding an optional argument with a backward-compatible default, or exposing a new library method. The distinction is not how many lines changed: a one-line new method can be a feature, while a large internal rewrite can remain a patch if it only corrects existing behavior and preserves the contract.
Optional response fields need more care. Adding archived may be compatible for clients that ignore unknown JSON properties, but not for clients whose schema validation rejects them. Treat the field as a minor addition only if your compatibility policy and supported client behavior make it safe. Otherwise, introduce it through a versioned or opt-in path.
Classify real changes by contract and impact
Use a shared table in pull requests or release review. It makes the team explain both the contract and the compatibility assumption instead of relying on labels like “small” or “safe.”
| Change | Likely bump | Reason or check |
|---|---|---|
| Fix a documented endpoint that returns the wrong total | Patch | Restores promised behavior; check that clients can accept the corrected value. |
| Add an optional filter that preserves results when omitted | Minor | Adds a capability without changing existing requests. |
| Add an output field | Minor, conditionally | Confirm supported clients tolerate unknown fields. |
| Change a documented default sort order | Major or staged change | Existing clients may depend on the old default. |
| Reject an input that was previously accepted | Major or staged change | Even if the input was considered invalid, clients may send it. |
These are starting classifications, not a substitute for checking the API’s promises. The SemVer specification says to use judgment: a behavior change that is technically a fix can still affect users enough to warrant a major release. Audience impact is part of responsible release planning.
Separate intended behavior from accidental behavior
The hardest patch decisions involve behavior that was never documented but has been present for a long time. Imagine an endpoint that has returned duplicate records for months. The team fixes the query, and the result is now correct. Is that a patch? Usually, if duplicates violated the documented contract and the correction doesn’t break clients that reasonably depend on the old output.
But “undocumented” does not automatically mean “safe to change.” If the output is consumed by a large installed base, users may have built workflows around it. SemVer communicates expectations; a version number cannot make a disruptive correction harmless. Consider a deprecation period, a temporary compatibility option, or a major release when the impact justifies it.
Also separate the correction from any new option. Fixing the duplicate records is the patch-sized part; adding a parameter that lets callers include duplicates is a new capability and therefore minor-sized. If the two changes ship together, release the highest applicable change: the minor version can contain both.
Walk one change from report to release
Suppose a library’s documented parseDate(input) function accepts ISO date strings, but returns the wrong date for strings with a UTC offset. A user reports the issue, and a fix corrects the result without changing the function signature. The team should work through the decision in order:
- Check the contract: Confirm that offset-bearing ISO strings are supported and that the documented result matches the corrected behavior.
- Check existing callers: Ask whether users could reasonably depend on the incorrect result. If the correction changes what their applications do, flag that impact rather than assuming “bug fix” means “no risk.”
- Check the scope: If the change only corrects this result and keeps existing calls valid, classify it as a patch. If the work also adds a new parsing mode or option, that addition is a feature.
- Record the decision: Write down the contract evidence and any compatibility caveat in the pull request and release notes.
If this is a stable 2.x API and the correction is compatible, the version would move from, for example, 2.4.1 to 2.4.2. If a new optional parsing mode ships in the same release, the feature makes it 2.5.0.
Make the team use the same decision rule
Agree on a short review checklist and apply it to every public API change. A useful order is:
- Is the changed behavior part of the public contract? Check documentation, tests, and supported usage—not only the implementation.
- Does the change correct a failure to meet that contract? If so, consider patch.
- Does it add an optional capability? If so, consider minor.
- Can existing supported clients break? If yes, investigate a major bump or a staged migration, regardless of the change’s intent.
- Could a correction disrupt a large user base? If yes, discuss a migration path even if the strict compatibility test suggests patch.
Ask the author to include a one-sentence classification in the pull request: “Patch: fixes the documented offset calculation; existing calls remain valid,” or “Minor: adds an optional filter; requests without it are unchanged.” That small explanation gives reviewers something concrete to challenge and makes later release notes easier to write.
Automate labels, not judgment
Conventional Commits and tools such as semantic-release can turn agreed labels into consistent releases. In a common configuration, a fix: commit triggers a patch release and a feat: commit triggers a minor release; a breaking-change marker can trigger a major release. The exact mapping depends on the tool’s configuration.
Automation can’t determine whether a corrected behavior will surprise existing users, whether an output field breaks strict clients, or whether an undocumented behavior has become an expectation. If a team maps every commit named fix to patch, a behavior correction with serious user impact can slip through without a compatibility discussion. Use automation after a human has classified the change; include the rationale in the pull request and check that the generated release notes match it.
There is also a practical cost to overusing minor releases: users may read them as a steady stream of new capabilities, while a patch gives a clearer signal that the release is corrective. Conversely, calling a feature a patch hides useful information from users deciding whether to upgrade. The purpose of the bump is to set an expectation, not reward the size of the work.
Set the policy this week
Pick one recent patch and one recent minor release. For each, write down the public contract, what changed for existing clients, and why the team chose that bump. If those explanations use different standards—such as “no signature change” in one case and “users can opt in” in another—your policy needs tightening.
Then add a short versioning checklist to your pull-request template and ask reviewers to classify public API changes as patch, minor, or potentially breaking. Include a compatibility note for new response fields, changed defaults, and corrected outputs. After a few releases, compare the labels with what users actually experienced and update the policy where it failed.
The working rule is simple: patch what the contract already promised; use minor for a compatible capability users didn’t have; treat compatibility and user impact as separate questions. That distinction gives maintainers a repeatable decision and gives users a more honest signal in the version number.