api-design-principles/skills/versioning-and-evolution/SKILL.md
This skill should be used when the user is designing API versioning strategy, choosing between URL path and header-based versioning, implementing Stripe-style date-based versioning, planning API deprecation, using sunset headers, or evolving an API without breaking clients. Covers URL versioning, additive evolution, backward compatibility, and deprecation communication.
npx skillsauth add oborchers/fractional-cto versioning-and-evolutionInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Your API version is a promise. The moment a consumer writes code against your endpoint, you have entered a contract. Breaking that contract -- renaming a field, removing an endpoint, changing a type -- destroys trust and costs integration partners real engineering time. The best APIs treat backward compatibility as non-negotiable and version changes as a carefully managed migration, not a surprise.
Stripe has kept /v1/ stable since 2012. Over a decade, zero URL-level version bumps. They ship breaking changes behind dated version headers, and every account stays pinned to the version it was created with. This is the gold standard. Your API should aspire to the same discipline.
Four strategies exist in production. Each has real tradeoffs.
| Strategy | Mechanism | Pros | Cons | Used By |
|----------|-----------|------|------|---------|
| URL path | /v1/resources | Explicit, visible, cacheable, trivial to route at the gateway | URL changes break clients on major bumps, encourages big-bang migrations | Stripe, Google, Twilio, Facebook, Spotify |
| Custom header | Stripe-Version: 2024-11-20 or X-GitHub-Api-Version: 2022-11-28 | Clean URLs, per-request granularity, enables incremental migration | Invisible in URLs, harder to debug and cache, must remember the header | Stripe (secondary), GitHub |
| Query parameter | ?api-version=2022-12-01 | Easy to add, visible in logs | Mixes versioning with resource params, easily omitted, pollutes query string | Azure, AWS |
| Date-based header (Stripe model) | Account pinned to signup date, override with Stripe-Version header | Granular per-request migration, no URL changes, each change documented by date | Requires version gate infrastructure internally | Stripe |
Default to URL path versioning. It is the most intuitive, most cacheable, and easiest to route. Every developer understands /v1/. Reserve header-based versioning for fine-grained control within a major version, following Stripe's hybrid model.
Use a single major version in the URL path and keep it stable for years.
/v1/. Do not increment to /v2/ unless you are fundamentally restructuring the entire API surface./v1/users and /v2/users are different cache keys with no extra configuration.Stripe has been on /v1/ since 2012. Google Cloud APIs use /v1/ and /v2/ across services. Twilio's legacy API bakes the date 2010-04-01 into every URL path. The takeaway: pick a version prefix and commit to it for the long term.
Within a major version, evolve the API using only additive, backward-compatible changes. This is the single most important rule for API stability.
Non-breaking changes (always safe):
rate-limiting-and-security skill)Breaking changes (never without a version bump):
| Change | Breaking? | Why |
|--------|-----------|-----|
| Add metadata field to response | No | Existing clients ignore unknown fields |
| Remove receipt_url from response | Yes | Clients relying on it will break |
| Rename source to payment_method | Yes | Field access by old name fails |
| Change amount from integer to string | Yes | Type coercion breaks parsers |
| Add optional ?expand[] parameter | No | Existing requests are unaffected |
| Make email required on create | Yes | Existing create calls without email fail |
| Add new paused status enum value | No | Only if clients handle unknown enums |
| Change 200 response to 201 for creation | Yes | Clients checking status codes break |
Treat new enum values as non-breaking only if your documentation instructs consumers to handle unknown values. Stripe does this explicitly -- their docs state that new enum values may be added at any time and client code should not break on unrecognized values.
Stripe operates the most sophisticated API versioning system in the industry. Understand it, then decide how much of it your API needs.
How it works:
Stripe-Version: 2024-11-20.acacia to use a specific version for a single request. This enables testing new versions without committing your entire integration.From Amber Feng (former Stripe engineering lead): "We have a single API server that handles all versions. We transform the response at the edge based on the requested version. This means we only have to maintain one set of business logic."
Version changelog entries are specific and actionable:
2024-11-20.acacia
- Removed `charges` field from PaymentIntent (use `latest_charge` instead)
- Changed `customer.discount` from object to array
- Renamed `pending_invoice_item_interval` to `pending_update_interval`
Each entry documents what changed, the old behavior, the new behavior, and migration instructions. Date-based versions like 2024-11-20 are more meaningful than v37 because developers immediately know when the version was released and can correlate it with their integration timeline.
When to adopt this model: If your API has many consumers, ships changes frequently, and cannot afford to break anyone, the Stripe model is worth the infrastructure investment. For smaller APIs with fewer consumers, URL path versioning with additive evolution is sufficient.
When retiring an old API version or endpoint, communicate early, loudly, and through multiple channels. RFC 8594 defines the Sunset HTTP header for exactly this purpose.
Return deprecation headers on every response from a deprecated endpoint:
HTTP/1.1 200 OK
Sunset: Sat, 01 Jun 2026 00:00:00 GMT
Deprecation: true
Link: <https://api.example.com/docs/migration-v2>; rel="successor-version"
X-API-Version: 2024-01-15
Follow a four-phase deprecation timeline:
Sunset and Deprecation response headers to every request hitting deprecated endpoints. Send follow-up emails with migration instructions. Track per-API-key usage of deprecated endpoints to identify who still needs to migrate.410 Gone with a response body that includes the migration guide URL. Do not return 404 -- a 410 explicitly signals that the resource existed but has been permanently removed.After sunset, return a helpful 410:
{
"error": {
"type": "api_version_error",
"message": "API version 2022-03-01 has been sunset. Please upgrade to version 2024-01-15 or later.",
"doc_url": "https://docs.example.com/api/migration/2024-01-15"
}
}
Headers alone are not enough. Consumers rarely inspect response headers in production unless something breaks. Use every channel available:
GitHub announces deprecations in their changelog, sends email notifications, provides migration guides, returns Warning headers on deprecated endpoints, and uses 410 Gone after sunset. Stripe goes further -- API versions are effectively never removed, and the dashboard shows which version each account is pinned to with an upgrade guide for each version transition.
Always return the API version used to process the request in a response header. This eliminates debugging guesswork.
HTTP/1.1 200 OK
X-API-Version: 2024-11-20
X-Request-ID: req_9ofKRcFXZEvl2X
Content-Type: application/json
If the consumer sends a version header, echo back the version that was actually applied. If no version was sent, return the default version for that account or API key. This is critical for debugging version-related issues -- support can immediately verify which version a consumer is hitting.
Stripe returns Stripe-Version in every response. GitHub returns X-GitHub-Api-Version. Make this standard practice.
Every version bump needs a migration guide. A bare changelog entry is not enough -- consumers need step-by-step instructions.
A good migration guide includes:
receipt_url moved behind expand[], show exactly how to update the API call.Stripe's migration format is a model to follow:
Before: charge.receipt_url (always present in response)
After: expand=["receipt_url"] to include (omitted by default)
Before: invoice.finalized_at = 1705996400 (Unix timestamp)
After: invoice.finalized_at = "2025-01-23T12:00:00Z" (ISO 8601 string)
Provide a version comparison endpoint or tool if your API has complex version differences. At minimum, link to the migration guide from every deprecation header, error message, and dashboard warning.
When designing or reviewing API versioning and evolution:
/v1/) that remains stable for yearsX-API-Version)Sunset and Deprecation headers with the removal date410 Gone with a migration guide URL, not 404tools
This skill should be used when the user invokes any /plan-* command from the planning-tools plugin (/plan-context, /plan-master, /plan-open-questions, /plan-verify, /plan-tick, /plan-progress, /plan-delete), asks how Claude Code's plan files work, asks where plans are stored, asks to author or audit a multi-phase master planning document, asks how to walk through a plan's Open Questions interactively, asks how to write progress entries, or mentions ~/.claude/plans/ or .claude/planning-tools.local.md. Provides the index of planning-tools commands, the master-plan workflow lifecycle, the v0.3.0+ list-shape mandate (phases and questions as headings + bulleted scope items, never tables), the v0.3.2+ plain-bullet shape (no `- [ ]` checkboxes — heading emoji is the sole tick signal), the progress-entry methodology, and the mechanics of Claude Code's plan-mode file storage.
testing
This skill should be used when the user is adjusting spacing, padding, margins, content density, section gaps, vertical rhythm, or separation between elements. Also applies when reviewing whether a design feels cramped or too sparse, choosing between borders and whitespace for separation, or defining a spacing system. Covers the 4px/8px spacing system, macro vs micro whitespace, content density spectrum, separation techniques (whitespace > background shifts > borders), and vertical rhythm.
development
This skill should be used when the user is defining brand personality in design, choosing between illustration and photography, adding motion or animation, creating visual motifs, ensuring layout variety, customizing CSS framework defaults, or calibrating the level of creative expression for a given context. Covers Lavie & Tractinsky's expressive aesthetics, the expression spectrum (restrained to bold), brand personality translation, illustration systems, photography direction, and template independence.
development
This skill should be used when the user is establishing visual importance, designing headings, creating focal points, designing CTAs or buttons, arranging label-data relationships, implementing scanning patterns (F-pattern, Z-pattern), or ensuring one dominant element per screen. Covers the three levers of hierarchy (size, weight, color), three-tier information architecture, the 'emphasize by de-emphasizing' principle, CTA design, and label-data relationships.