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 claudepluginhub oborchers/fractional-cto --plugin api-design-principlesThis skill uses the workspace's default tool permissions.
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 surpr...
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
Retrieves current documentation, API references, and code examples for libraries, frameworks, SDKs, CLIs, and services via Context7 CLI. Ideal for API syntax, configs, migrations, and setup queries.
Uses ctx7 CLI to fetch current library docs, manage AI coding skills (install/search/generate), and configure Context7 MCP for AI editors.
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 404