API changelog and versioning strategy for partners
Versioning schemes, changelog discipline, and a deprecation policy that external partners can plan around, so integrations keep working while the API evolves.
Partners do not integrate with your current API. They integrate with the API as it existed the week they shipped, and they will still be calling it two years later. Every silent rename or removed field is a production incident on their side, then a support incident on yours.
Versioning is how you keep changing the product without surprising people who already committed. A changelog is how they find out. A deprecation policy is how they get time to move. The three only work together. A version number with no changelog is a rumor. A changelog with no sunset date is a suggestion. A sunset with no overlapping versions is a threat.
This pairs with the partner-ready API checklist, where a dated changelog is one of the five things a partner engineer looks for, and with API documentation best practices. Here we go deep on the scheme, the log, and the policy.
The 60-second version
If you only read one section, read this one:
- Never change the meaning of an existing version. Additive changes land in place. Breaking changes get a new version and a long overlap.
- Pick one scheme and stick to it: path, header, or date-based. Mixing them is how partners lose the thread.
- Write the changelog as a product, not a git dump. Dated entries, breaking versus additive, a migration note.
- Deprecation is a process: announce, overlap, remind, sunset. Measure the window in months.
- Tell partners in more than the changelog. Email the owner, flag it in the dashboard, put it in headers.
- SDKs, webhooks, and docs inherit the version. A library that tracks "latest" is a breaking-change pipe.
- Track who is still on the old version and talk to them before you turn it off.
- Publish the policy itself. Partners should be able to quote your definition of breaking and your notice period.
What partners actually need from versioning
Internal teams can Slack the author of a change. External partners cannot. They need three guarantees, in writing.
Stability of what they already call. A field they parse today still parses tomorrow, on the same version, with the same type and meaning. New fields can appear. Old fields do not vanish, rename, or change shape.
A way to opt into new behavior. When you fix a mistake or ship a better default, partners move on purpose, not because you flipped a global switch. That opt-in is the version.
Time and a map when they must move. If a version is going away, they need a date, a diff, and a working successor. "We deprecated v1" with no date and no migration guide is not a policy.
Those three are the product. The scheme you pick is an implementation detail that has to serve them. You cannot coordinate twenty partner engineering teams onto your calendar.
The three versioning schemes
Almost every public API ends up in one of three places. All three can work. Mixing them, or changing scheme mid-life, is what fails.
Path versions. The version sits in the URL, /v1/contacts and /v2/contacts. Obvious in logs and docs. Awkward when one resource change forks the whole tree.
Header versions. The URL stays stable and the partner sends a version header. You can evolve one resource without forking. The cost is invisibility: a missing header plus a moved default, and a partner on an old snippet calls a different contract. Default missing versions to the version the partner was created on, not to "latest."
Date-based versions. The partner pins a date and you keep that snapshot working. Additive monthly changes can share a name. Breaking changes get a new date. This is the model documented in Stripe's API versioning. It is excellent once you never mutate a date, and overhead if you ship two breaking changes a year.
| Scheme | What the partner pins | Strength | Failure mode |
|---|---|---|---|
Path (/v1) |
A URL prefix | Obvious in every log and screenshot | Whole-tree forks for small breaks |
| Header | A request header | URLs stay stable | Silent default-to-latest |
| Date | A date snapshot | Fine-grained, long overlap | Process-heavy if you are small |
Pick one. Write it at the top of the API overview. If you already have /v1 in production, do not "clean it up" into headers. Stability beats elegance.
Use a coarse public version (v1, v2, or a date). Keep semver for the spec document, the SDKs, and the changelog headings. Partners cannot pin patch-level URLs for every additive change.
What counts as a breaking change
The scheme only helps if everyone agrees on what forces a new version. Publish this list. Then use it in code review.
Additive, and they can land on the current version: a new resource, endpoint, or event; a new optional request field with the old default preserved; a new response field; a new error code for a new failure; a new optional enum value if you never promised the enum was closed.
Breaking: removed or renamed field, endpoint, parameter, or event; changed type or format; a new required field; a changed default; stricter validation; changed auth, pagination, or error envelope; a closed enum, or a changed meaning of an existing value.
The gray area is behavioral: same shape, different meaning. Pagination that used to be offset and is now cursor-shaped, a flipped sort, a smaller rate-limit window. Treat behavioral change as breaking.
| Change | Version action | Changelog |
|---|---|---|
| New optional field or endpoint | Same version | Additive entry |
| Renamed field | New version | Breaking, with mapping |
| New required field | New version | Breaking, with defaulting guidance |
| Stricter validation or changed default | New version or long warn-then-reject | Breaking |
| Performance-only | Same version | Note if clients will feel it |
If you are unsure, it is breaking. You cannot un-break a partner's production parser. If partners switch on code, changing error codes is breaking. Design the catalog once, in the spirit of API error design, and add codes rather than recycling them.
Changelog discipline
A changelog is not a reverse-chronological dump of commit subjects. It is the document a partner engineer opens when something broke at 11pm, and the document they read before a migration.
One canonical page. Not a Slack channel, not "see GitHub releases." A dated page in the docs, linked from the overview, every reference page, and the SDK README. RSS or a mailing list on top is good. The page is the source.
Every entry has a date, a version, and a class. Additive, behavioral, or breaking. If everything is "improvements," they stop reading.
Breaking entries include a map. Old field to new field. What happens if they do nothing. A link to a migration guide if the change is larger than a paragraph.
Ship the changelog with the change. The docs deploy is part of the API deploy. Keep it boring: "Added tax_id to Customer." "Deprecated GET /v1/users; use GET /v1/contacts. Sunset on 2027-03-01." The OpenAPI spec should move in the same commit.
A partner evaluating you will read the last six months to see whether you break things quietly. Treat the log as part of developer onboarding.
A deprecation policy partners can plan around
Deprecation is not an announcement. It is a sequence you run the same way every time.
1. Announce. Named change, old behavior, new behavior, date the old path stops, where to read the map. Publish it on the changelog and send it to the integration owners you have.
2. Overlap. Old and new versions both work. The old path keeps its meaning. You may add warning headers. You do not change results.
3. Remind. Reach partners still on the old version from telemetry. Dashboard banner, email to the technical contact.
4. Sunset. On the published date, the old path returns a stable error, not a surprise 404. Keep the old docs visible, marked retired.
For an external partner API, think in quarters. Six to twelve months is honest for a version that runs in production. If you cannot staff two versions that long, you cannot afford the break. Ship additively instead.
The Sunset header, defined in RFC 8594, lets a response advertise when a resource is expected to become unresponsive. It is a hint. Put it on deprecated routes and keep the date in the docs in sync.
| Stage | What partners see | What you track |
|---|---|---|
| Announce | Changelog, email, header on the old route | Who was notified |
| Overlap | Both versions work, warnings on the old | Traffic per version |
| Remind | Direct notes to remaining callers | List of stragglers |
| Sunset | Stable error, old docs marked retired | Zero traffic, or a named exception |
Write exceptions down. A security hole can force a shorter window. Say so, and help with the migration. Do not use "security" as cover for a cleanup you did not schedule.
Making the version real across the surface
The HTTP version is only the start. Every other client can deliver a different contract by accident.
SDKs should pin, not float. A library tagged 3.2.0 should send a known API version. If the SDK silently uses the account default, a dashboard click can break every partner who imported you. Generate clients from the spec for that version, as in the OpenAPI workflow, and major the library when the API majors.
Webhooks need a version too. An event body is a contract. Pin the payload version on the endpoint when the partner creates it. Document delivery the way you document webhook best practices: signed, retried, shaped stably.
Docs and CLI pin the same contract. Keep v1 docs up through sunset. The CLI should default to the pinned profile, not to whatever you shipped this morning.
The changelog is necessary and not sufficient. Email the technical contact, show a dashboard banner, and put Sunset headers on the deprecated route. Two reminders before sunset, from a human. If you cannot name the owner of an integration, you cannot deprecate it safely. While two versions run: route by the pin, fork the serializer so v2 fields do not leak into v1, test both in CI. A tighter rate limit is a behavioral break. See API rate limiting.
Common mistakes, and the fix
Defaulting missing versions to latest. The fix: pin each partner, key, or account to the version they started on. Latest is an opt-in.
Calling a change additive because the JSON still parses. The fix: if behavior, defaults, or validation changed, treat it as breaking.
A changelog that is a release-notes dump. The fix: date, version, class, map, migration link. Ship it in the same deploy as the change.
Announcing deprecation in one blog post and never again. The fix: announce, overlap, remind from telemetry, then sunset. Multiple channels, including the technical contact.
Sunsetting in weeks because the old code is annoying. The fix: measure the window in months. If you cannot carry two versions that long, do not break.
SDKs and webhooks that ignore the version. The fix: pin libraries and event endpoints to a known contract.
FAQ
Should we put the version in the URL or in a header? Either works if you never change the meaning of a pinned version and you do not default missing values to latest. Path versions are easier to see. Header and date versions keep URLs stable. Pick one scheme and leave it.
What belongs in an API changelog for partners? A single dated page with every additive, behavioral, and breaking change, the version it landed in, and a migration map for anything breaking. Ship it with the change.
How long should we keep an old API version alive? Long enough that a partner team can put the work on a real roadmap, usually months, often two quarters or more. Security fixes can shorten the window. Convenience should not.
Is a new required field a breaking change? Yes. Add it as optional, or introduce it on a new version with a documented default. Same rule for stricter validation and changed defaults.
How do we tell partners a version is going away? Publish the date and the map in the changelog, email the technical contact, warn in product, and add a Sunset header. Remind remaining callers from telemetry before the old path becomes a stable error.
Do SDKs need their own versions? Yes. A library version should send a known API version. If the SDK follows "latest," a change on your side can break partners who never touched their code.
The short version
Partners pin the API they shipped against, not the API you wish they used. Pick one versioning scheme, never mutate a pinned version, and put every additive and breaking change in a dated changelog. Breaking work gets a new version, an overlap measured in months, reminders to the people who still call the old path, and a sunset that returns a documented error. SDKs, webhooks, and docs pin the same contract. The policy itself is public, so a partner can plan.
Do that and you can keep evolving the product. Skip it and every cleanup becomes a surprise outage.
If you want a versioning and deprecation policy that matches your partner mix, that is exactly what a Partner Audit is for. We review the API, the changelog, and who is still on old contracts, then give you a scheme, a notice period, and a sunset you can keep.
Further reading
- RFC 8594, The Sunset HTTP Header Field: the standard way to advertise when a resource is expected to become unresponsive.
- Stripe API versioning: a public, date-based pinning model and how official clients send a version.
- OpenAPI Specification: version the description document with the API it describes.