This document defines how the Stellar Micro-Donation API is versioned, how releases are published, and how contributors should reason about breaking vs. non-breaking changes.
- Semantic Versioning Overview
- Version Number Rules
- Breaking vs. Non-Breaking Changes
- API Endpoint Versioning
- Release Flow
- Hotfix Releases
- Deprecation Policy
- Changelog Requirements
- Pre-release Labels
This project follows Semantic Versioning 2.0.0 (SemVer). Every released version is represented as:
MAJOR.MINOR.PATCH
| Segment | When it increments |
|---|---|
MAJOR |
Incompatible / breaking API changes |
MINOR |
New backwards-compatible functionality |
PATCH |
Backwards-compatible bug fixes |
Example progression:
1.0.0 → Initial stable release
1.1.0 → New /wallets/batch endpoint added
1.1.1 → Fix edge case in idempotency key validation
2.0.0 → Donation payload field renamed (breaking)
The current version is always reflected in package.json under the "version" field.
- Remove or rename an existing API endpoint
- Remove or rename a required or optional request field
- Change the meaning or type of an existing response field
- Change authentication behaviour in a way that invalidates existing API keys
- Drop support for a previously supported Node.js major version
- Alter database schema in a way that is not backwards-compatible
- Add a new endpoint
- Add new optional request fields (old requests still work)
- Add new fields to existing responses (consumers must be tolerant of unknown fields)
- Add a new feature flag or configuration option
- Add new npm scripts that do not affect runtime behaviour
- Expand rate-limit allowances
- Fix a bug without changing the public interface
- Improve performance without altering observable behaviour
- Update documentation only (prefer
docs:commit prefix instead of a release) - Update dependency patch/minor versions (security patches, etc.)
- Fix a typo in an error message that does not affect parsing
A change is breaking if a consumer that worked correctly with version N can fail or behave differently after upgrading to version N+1 without any code changes on their side.
Common examples for this project:
| Area | Breaking example |
|---|---|
| Donations API | Renaming amount → donation_amount in POST /donations |
| Wallets API | Removing GET /wallets/:id/balance |
| Auth | Requiring a new mandatory header (X-Api-Version) |
| Response shape | Changing { "id": "..." } to { "donationId": "..." } |
| Errors | Changing HTTP status codes for existing error conditions |
| Scheduler | Removing a recurring-donation frequency type (e.g. weekly) |
Breaking changes must:
- Increment
MAJORversion - Be documented in
CHANGELOG.mdunder a### Breaking Changessub-heading - Trigger an API path version bump (see API Endpoint Versioning) if the change affects a public route
- Be announced with a minimum 4-week deprecation notice before the old behaviour is removed (see Deprecation Policy)
A change is non-breaking (backwards-compatible) if existing consumers can upgrade without any modification.
Common examples:
| Area | Non-breaking example |
|---|---|
| Donations API | Adding an optional note field to POST /donations |
| Wallets API | Adding a new GET /wallets/:id/stats endpoint |
| Response | Adding a new createdAt field to existing responses |
| Config | Adding a new optional environment variable |
| Docs | Updating README, guides, or inline comments |
Non-breaking additions increment MINOR; fixes increment PATCH.
URL-based versioning is used to isolate breaking change surfaces:
/v1/donations
/v1/wallets
/v2/donations ← introduced with breaking change in MAJOR v2
Rules:
- All routes are prefixed with
/v{MAJOR}(e.g./v1,/v2). - A new URL version is introduced only on a MAJOR bump.
- The previous URL version remains available for the deprecation window (minimum 4 weeks after the new MAJOR release).
MINORandPATCHreleases never introduce a new URL prefix — they are additive to the existing prefix.
When a new URL version is introduced:
- Create a new route file (e.g.
src/routes/v2/donations.js). - Mount both old and new versions in
app.jsduring the deprecation window. - Add a
Deprecationresponse header to the old version:Deprecation: version="v1", sunset="2026-06-30"
feature/fix branch → main (develop) → release tag → GitHub Release
-
All work merges into
mainvia pull request (see Branch Protection). -
Determine the version bump using the rules above. When in doubt, discuss in the related issue or pull request before releasing.
-
Update
CHANGELOG.mdwith all changes since the last release (see Changelog Requirements). -
Bump the version in
package.json:# Patch fix npm version patch --no-git-tag-version # New feature npm version minor --no-git-tag-version # Breaking change npm version major --no-git-tag-version
--no-git-tag-versionis used so the tag is created separately after review. -
Commit the version bump:
git add package.json pnpm-lock.yaml CHANGELOG.md git commit -m "chore: release v$(node -p "require('./package.json').version")" -
Create and push a signed tag:
VERSION=$(node -p "require('./package.json').version") git tag -a "v$VERSION" -m "Release v$VERSION" git push origin main --follow-tags
-
Create a GitHub Release from the tag:
- Title:
v{VERSION} - Body: copy the relevant section from
CHANGELOG.md - Mark as pre-release if the label is
alphaorbeta
- Title:
-
CI pipeline runs automatically on the new tag. All required checks (tests, coverage, lint) must pass — see CI Pipeline.
For critical production bugs that cannot wait for the normal release cycle:
-
Branch off the latest release tag:
git checkout -b hotfix/v1.2.1 v1.2.0
-
Apply the minimal fix, add a test covering the regression.
-
Follow steps 3–8 of the Release Flow directly on the hotfix branch.
-
Merge the hotfix branch back into
main:git checkout main git merge --no-ff hotfix/v1.2.1 git branch -d hotfix/v1.2.1
Hotfixes always produce a PATCH increment unless the fix itself introduces a breaking change (rare; if so, treat as a MAJOR).
When a feature, endpoint, or behaviour is scheduled for removal:
-
Announce the deprecation in the GitHub issue tracker with the label
deprecationat least 4 weeks before the MAJOR release that removes it. -
Log a deprecation warning server-side when the deprecated path is exercised:
console.warn('[DEPRECATED] GET /v1/wallets/:id/balance — use GET /v2/wallets/:id/balance instead. Removal scheduled for v2.0.0.');
-
Add
DeprecationandSunsetresponse headers (RFC 8594) on affected endpoints, plus aLinkheader pointing to migration docs:Deprecation: true Sunset: Wed, 01 Oct 2026 00:00:00 GMT Link: </docs/VERSIONING_STRATEGY.md#migration-guide>; rel="deprecation"The
Sunsetdate must be at least 4 weeks after the first deprecation announcement. -
Document the migration path in
CHANGELOG.mdand the relevantdocs/guide. -
Remove the deprecated code only after the sunset date has passed and the MAJOR version has been released.
For every endpoint or behaviour that is deprecated, the following must all be done before the MAJOR release:
| Step | Where |
|---|---|
Add Deprecation: true header |
route handler |
Add Sunset: <RFC 7231 date> header |
route handler |
Add Link: <url>; rel="deprecation" header |
route handler |
Log a server-side DEPRECATION warning |
route handler |
Create a GitHub issue labelled deprecation |
GitHub |
Add a ### Deprecated entry to CHANGELOG.md |
CHANGELOG.md |
Document the migration path in docs/ |
docs/ |
- Minimum notice period: 4 weeks from the first public announcement.
- Recommended: align the sunset date with the next scheduled MAJOR release.
- Express the date in RFC 7231 format:
DDD, DD MMM YYYY HH:MM:SS GMT.
This section is the end-to-end worked example required by issue #1187.
GET /donations/export is a synchronous, streaming export introduced in #919.
It was superseded by the async job-based POST /donations/export (+ GET /donations/export/:jobId/download), which is safer for large datasets and plays well with timeouts and load-balancers.
-
Headers emitted on every
GET /donations/exportresponse:Deprecation: true Sunset: Wed, 01 Oct 2026 00:00:00 GMT Link: </docs/VERSIONING_STRATEGY.md#deprecation-worked-example-get-donationsexport>; rel="deprecation" -
Server-side warning logged at
WARNlevel whenever the endpoint is called:[DEPRECATION] GET /donations/export is deprecated — use POST /donations/export instead. Removal scheduled for v2.0.0 (2026-10-01). -
CHANGELOG.md entry added under
### Deprecated. -
Sunset date: 2026-10-01 — at least 4 weeks from the deprecation notice, aligned with the planned v2.0.0 release.
| Old (deprecated) | New (recommended) |
|---|---|
GET /donations/export?format=csv |
POST /donations/export → poll GET /donations/export/:jobId → download GET /donations/export/:jobId/download |
Step-by-step migration:
# 1. Start the async export job
curl -X POST https://api.example.com/donations/export \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"format":"csv","startDate":"2026-01-01","endDate":"2026-06-30"}'
# → { "jobId": "abc123", "status": "queued" }
# 2. Poll until complete (status = "done")
curl https://api.example.com/donations/export/abc123 \
-H "X-API-Key: $API_KEY"
# → { "jobId": "abc123", "status": "done", "downloadUrl": "..." }
# 3. Download the file
curl "https://api.example.com/donations/export/abc123/download" \
-H "X-API-Key: $API_KEY" -o donations.csvClients that currently consume the synchronous streaming response should switch to the async flow before 2026-10-01.
Every release must include a CHANGELOG.md update. Use Keep a Changelog format:
## [1.2.0] - 2026-03-15
### Added
- `GET /v1/wallets/:id/stats` endpoint for donation analytics (#185)
- Optional `note` field on `POST /v1/donations`
### Changed
- Rate limit window for donation endpoints increased from 15 min to 30 min
### Deprecated
- `GET /v1/wallets/:id/balance` — use `GET /v1/wallets/:id/stats` instead (removal in v2.0.0)
### Fixed
- Idempotency key not persisted on Stellar network timeout (#201)
### Security
- Upgraded `stellar-sdk` to patch CVE-XXXX-XXXXRules:
- Entries are written for humans, not machines — describe the impact, not the diff.
- Every entry links to the relevant GitHub issue or PR number.
### Breaking Changesis a required sub-section under any MAJOR release entry.- Do not leave an
[Unreleased]section in the file at release time — rename it to the version and date.
For early testing of MAJOR changes, pre-release versions may be published:
| Label | Format | Meaning |
|---|---|---|
| Alpha | 2.0.0-alpha.1 |
Internal testing only; API may change |
| Beta | 2.0.0-beta.1 |
External testing; API is stabilising |
| Release Candidate | 2.0.0-rc.1 |
Feature-complete; only critical fixes accepted |
Pre-releases:
- Are tagged in Git with the full label (e.g.
v2.0.0-alpha.1). - Are marked as pre-release in GitHub Releases.
- Are never the default installation target.
- Must still pass CI (tests, coverage, lint) before tagging.
| Change type | Version bump | New URL prefix? |
|---|---|---|
| Remove or rename endpoint | MAJOR | Yes |
| Remove or rename request/response field | MAJOR | Yes |
| Add new optional field | MINOR | No |
| Add new endpoint | MINOR | No |
| Bug fix | PATCH | No |
| Docs only | No release needed | No |
| Security patch (no API change) | PATCH | No |
Related documents: