From harness-claude
Guides URL path API versioning (/v1/, /v2/) for public APIs, covering prefix placement, major-only versioning, gateway routing, support windows, and migration patterns.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> URL PATH VERSIONING EMBEDS THE API VERSION DIRECTLY IN THE URI (/v1/, /v2/) — IT TRADES CLEAN URI SEMANTICS FOR MAXIMUM VISIBILITY AND CACHEABILITY, MAKING IT THE DEFAULT CHOICE FOR PUBLIC APIs WHERE DEVELOPER EXPERIENCE AND REVERSE-PROXY ROUTING SIMPLICITY OUTWEIGH STRICT REST PURITY.
Guides API versioning strategies including URL path, header, query parameter, and content negotiation. Helps manage breaking changes, deprecations, and multiple versions.
Implements API versioning using URL paths, headers, or query parameters with deprecation timelines, backward compatibility, and migration strategies. Use for managing multiple versions or planning breaking changes.
Implements API versioning via URL paths, headers, or query params with backward compatibility, deprecation headers, migration paths, and OpenAPI-based breaking change detection.
Share bugs, ideas, or general feedback.
URL PATH VERSIONING EMBEDS THE API VERSION DIRECTLY IN THE URI (/v1/, /v2/) — IT TRADES CLEAN URI SEMANTICS FOR MAXIMUM VISIBILITY AND CACHEABILITY, MAKING IT THE DEFAULT CHOICE FOR PUBLIC APIs WHERE DEVELOPER EXPERIENCE AND REVERSE-PROXY ROUTING SIMPLICITY OUTWEIGH STRICT REST PURITY.
Version prefix placement — The version identifier belongs in the first path segment, immediately after the host: https://api.example.com/v1/users. Placing it deeper (/users/v1) breaks routing rules and makes wildcard proxying fragile. The prefix applies to the entire API surface under it — never to individual resources.
Major-only versioning — URL versions track breaking changes only. Minor and patch changes (new optional fields, new endpoints, new query parameters) are additive and do not warrant a new version prefix. Incrementing the URL version for non-breaking changes wastes consumer migration effort and signals instability. Follow the rule: if existing clients break, bump the URL version; if they do not, they should not need to.
Simultaneous version support window — Running v1 and v2 in parallel is operationally expected. Define a support lifecycle at launch: how long v1 is maintained after v2 ships, what SLA applies to each version, and when v1 enters maintenance-only mode. Stripe supports each API version indefinitely; GitHub runs two major versions concurrently; Twilio documents explicit sunset dates per version.
Version routing at the gateway — URL versioning enables path-based routing at the API gateway, load balancer, or reverse proxy without any header inspection. A single nginx rule location /v2/ routes to the v2 cluster. This simplicity is a primary reason URL versioning dominates public API design despite REST purists preferring header-based negotiation.
Migration timeline patterns — Announce v2 at least 6 months before deprecating v1 for external consumers. Publish a migration guide listing every breaking change with before/after examples. Emit Deprecation and Sunset headers on v1 responses (see api-deprecation-strategy) so tooling can surface warnings automatically. Provide a changelog entry for each version at a stable URL (/v1/changelog).
Semantic versioning alignment — URL versions align with semver's major version number. /v1/ corresponds to semver 1.x.x; /v2/ to 2.x.x. Minor increments (1.1, 1.2) are expressed through response body evolution (new optional fields, new endpoints) and documented in changelogs — never through new URL segments.
Stripe's API is the canonical example of production URL versioning at scale. The base URL embeds the major version:
v1 request — list customers:
GET /v1/customers?limit=10
Authorization: Bearer sk_example_...
HTTP/1.1 200 OK
Content-Type: application/json
{
"object": "list",
"data": [ ... ],
"has_more": true,
"url": "/v1/customers"
}
Stripe's versioning model adds a second dimension: request-date versioning via the Stripe-Version header, but the URL path /v1/ remains fixed across all Stripe versions. This shows that URL versioning and header versioning are not mutually exclusive — the URL anchors the major API generation while the header gates fine-grained behavioral changes.
GitHub API URL versioning (REST v3 → REST API):
GET /v3/repos/octocat/Hello-World
Accept: application/vnd.github.v3+json
Authorization: Bearer ghp_...
GitHub migrated from /v3/ implicit to the explicit REST API base. Their migration guide published all endpoint path changes with redirects from old paths, enabling clients to migrate incrementally.
Twilio API versioning:
POST /2010-04-01/Accounts/{AccountSid}/Messages
Authorization: Basic ...
Content-Type: application/x-www-form-urlencoded
Twilio uses a date-based URL version (2010-04-01) rather than an integer, which conveys stability and timestamp semantics — useful when the API is updated infrequently and date-anchored releases carry meaning for audit and compliance consumers.
Versioning every minor change. Creating /v1/, /v2/, /v3/ for each additive field addition or new endpoint signals poor discipline and forces consumers into unnecessary migration work. URL versions should be rare, significant, and announce breaking changes. If you are on /v7/ after two years, your versioning granularity is wrong.
Embedding version mid-path. GET /users/v2/profile creates per-resource versioning that makes routing tables explode and makes it impossible to version the entire API coherently. Clients cannot construct a base URL for "the v2 API" — they must track per-resource versions. Always version at the root segment.
Silently breaking v1 during v2 development. Running v1 and v2 from the same codebase with feature flags that silently alter v1 behavior is a contract violation. Version boundaries must be hard: a request to /v1/ must behave identically to its original contract regardless of v2 development activity. Use separate deployments or strict branch-by-abstraction patterns.
No sunset timeline at launch. Shipping v2 without announcing when v1 will be retired leaves consumers in limbo. They cannot prioritize migration work without a date. Publish a deprecation schedule with v2's launch, even if it is tentative.
Strict REST theorists argue that versioning in the URI violates the principle that a URI identifies a resource, not a resource-at-a-point-in-time. Under this view, /v1/users/42 and /v2/users/42 are two different resources, but they represent the same underlying entity. The counter-argument from pragmatists (and most API teams): URI pollution is a minor aesthetic cost worth paying for the operational benefits of path-based routing, browser history clarity, and copy-pasteable URLs that are immediately interpretable by developers. The REST purity argument is correct in theory; URL versioning wins in practice for public APIs.
URL versioning enables aggressive CDN caching by version prefix. A CDN rule can cache all /v1/ responses with a long TTL while purging only /v2/ on rollout. Header-versioned APIs require Vary: Accept or Vary: API-Version headers, which most CDNs handle poorly — resulting in cache fragmentation or disabled caching entirely. For read-heavy public APIs, this is a meaningful performance argument for URL versioning.
Stripe has maintained /v1/ as its URL prefix since 2011 — over a decade — while making hundreds of breaking changes via their date-based Stripe-Version header. Their approach treats the URL prefix as the API generation (REST API vs. a hypothetical v2 with a fundamentally different paradigm) and the header as the fine-grained version. This two-tier model has allowed Stripe to serve customers using 2012-era API versions alongside customers using today's version from the same /v1/ base path. The result: zero forced migration events for existing integrations, and a developer reputation that directly contributes to adoption. By contrast, Twilio has sunset multiple URL versions, requiring migration guides and support overhead that Stripe has avoided.
Deprecation and Sunset response headers to all v1 endpoints pointing to the migration guide URL./changelog or /migration/v1-to-v2) with before/after examples for every changed endpoint.harness validate to confirm skill files are well-formed and related skills are correctly cross-referenced./v1/) with no per-resource version segments.