Public APIs need versioning the moment a third party builds against them. Get it wrong and every breaking change is a customer-relationship event. Get it right and you can evolve forever. This post is the working playbook.

Three versioning shapes

URL versioning

/v1/users
/v2/users
  • Pros: Obvious. Easy to grep. Easy to support multiple versions in code (different routers).
  • Cons: Big version bumps required for any breaking change.

Header versioning

GET /users
Accept: application/vnd.example.v1+json
  • Pros: Cleaner URLs. Versioning orthogonal to resource paths.
  • Cons: Less discoverable. Harder to document. Browser GET requests can’t easily set headers.

Date-based (Stripe-style)

GET /users
Stripe-Version: 2026-04-01

Version is a date. Each customer pins a date. Server keeps multiple “behaviors” alive.

  • Pros: Fine-grained migration. Customers move on their own schedule.
  • Cons: Server-side complexity (per-version behavior tables).

What to pick

Best for
URL (/v1/)Public APIs with anonymous consumers, mobile apps
HeaderInternal microservices, hypermedia APIs
DateSaaS APIs with controlled SDK distribution (Stripe model)

For 2026 startups: URL. Boring, debuggable, supported by every tool.

What counts as “breaking”

Breaking:

  • Removing a field.
  • Renaming a field.
  • Changing a field’s type.
  • Tightening validation (e.g., previously accepted strings now rejected).
  • Changing default values that affect behavior.
  • Removing an endpoint.
  • Changing an endpoint’s auth requirements.

Non-breaking:

  • Adding a new field (clients ignore unknown fields).
  • Adding a new endpoint.
  • Loosening validation (e.g., now accept formats you didn’t before).
  • Adding new optional query parameters.

For non-breaking, no version bump. Just ship.

Deprecation timeline

A respectful deprecation:

  1. Announce in changelog + email + dashboard banner. “v1 deprecated, will be removed on YYYY-MM-DD.”
  2. Add Deprecation: header to v1 responses. RFC 8594 standard:
Deprecation: true
Sunset: Wed, 01 Apr 2027 00:00:00 GMT
Link: </v2/users>; rel="successor-version"
  1. Track adoption. Per-customer dashboards: % of calls on v1 vs v2.
  2. Outreach to laggards. A month before sunset, email customers still on v1.
  3. Sunset. Old version returns 410 Gone with a helpful message.

The minimum reasonable deprecation window: 12 months for B2B SaaS, 24 for enterprise.

Versioning your data, too

API versioning is the surface; underlying data changes are also breaking. Strategies:

  • Add nullable columns. Never drop columns from a serving table while old API versions read them.
  • Materialized projections for old API shapes from the new schema.
  • Migration scripts that run separately from API versioning.

For database-side patterns see PostgreSQL 18 Features — pg_createsubscriber for zero-downtime migrations.

Common mistakes

1. Bumping the version for every change

If every PR requires a major bump, you’ll have v47 within a year. Reserve major bumps for genuinely breaking changes.

2. No deprecation policy

Customers find out v1 is gone via 404. Bad relationship. Always announce, monitor adoption, then sunset.

3. Inconsistent versioning across endpoints

Some endpoints are /v1/, others are /api/v2/. Pick one. Document. Stick with it.

4. Versioning without integration tests

Without tests, you don’t know which clients use which version. Track usage at the per-endpoint level.

5. Killing v1 before v2 is feature-complete

Customers can’t migrate if v2 doesn’t have the feature they rely on. v2 must be a strict superset (feature-wise) before v1 sunsets.

Read this next

If you want my API-deprecation runbook with templates, it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .