Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

API Versioning Strategies Compared

By Kokil Thapa | Last reviewed: September 2026

Choosing how to version a REST API locks in client contracts for years. When API versioning strategies compared side by side, the trade-offs become obvious: URL paths are simple to debug, headers keep URLs clean, and query parameters sit somewhere in between. I've shipped public APIs on Laravel 12 and 13 for legal-tech portals, eCommerce carts, and partner integrations — and the wrong versioning choice usually surfaces months later, during a breaking change nobody planned for. This guide walks through each approach with copy-pasteable patterns, a decision table, and the deprecation rules that keep production stable.

Before you pick a strategy, read how building RESTful APIs with Laravel structures routes and resources. Public APIs also need auth, throttling, and docs — topics covered in our API development service and companion posts on Laravel API best practices.

How Do API Versioning Strategies Compare for REST APIs?

Every versioning method answers one question: how does a client request a specific contract? The answer affects caching, SDK design, gateway rules, and how painful breaking changes become.

Breaking changes include removing fields, renaming properties, changing validation rules, or altering HTTP semantics. Non-breaking additions — new optional fields, new endpoints — typically stay on the current version. The strategy you choose should make the version visible at the edge and traceable in logs.

Four Common API Versioning StrategiesURI Path/api/v1/usersQuery Param?version=2Accept Headervnd.app.v2+jsonContent-TypeSubdomainv2.api.example.comClient RequestSelects contract version before routingAPI Gateway / RouterRoutes to v1 or v2 controller logic
API versioning strategies compared: four ways clients signal which contract they expect
StrategyExampleClient frictionCache-friendlyBest fit
URI path/api/v1/ordersLowHighPublic REST APIs, mobile apps, third-party SDKs
Query parameter/api/orders?version=2LowMediumInternal tools, gradual rollout behind flags
Accept headerAccept: application/vnd.app.v2+jsonMediumMediumStrict hypermedia APIs, vendor media types
Custom headerApi-Version: 2MediumLowWhen URLs must stay identical across versions
Subdomainv2.api.example.comHighHighLarge platforms with separate deploy units
No explicit versionAdditive-only evolutionLowestHighEarly-stage products with few external clients

For most teams shipping Laravel 13 on PHP 8.3+, URI path versioning is the default I'd recommend. It shows up clearly in access logs, works with any HTTP client, and aligns with how Stripe, GitHub, and Shopify document their Admin API (2026-07 or later).

What Is URI Path Versioning and When Should You Use It?

URI path versioning embeds the major version in the URL segment. Clients call /api/v1/users today and migrate to /api/v2/users when ready. Proxies, CDNs, and WAF rules can route or block by path prefix without parsing headers.

Laravel 13 route groups

In Laravel 13, group versioned routes under a prefix and namespace. Keep v1 and v2 in separate controller directories so diffs stay readable during refactors.

// routes/api.php
Route::prefix('v1')->group(base_path('routes/api/v1.php'));
Route::prefix('v2')->group(base_path('routes/api/v2.php'));
// routes/api/v1.php
use App\Http\Controllers\Api\V1\UserController;

Route::get('/users', [UserController::class, 'index']);
Route::get('/users/{user}', [UserController::class, 'show']);
// routes/api/v2.php
use App\Http\Controllers\Api\V2\UserController;

Route::get('/users', [UserController::class, 'index']);

Use separate Form Requests and API Resources per version. Sharing a single Resource class between v1 and v2 creates hidden coupling — a field rename in v2 breaks v1 serialization silently.

When path versioning wins

  • Public partner APIs where clients copy URLs from documentation
  • Mobile apps that hard-code base URLs per release
  • API gateways (Kong, Traefik) that rate-limit by path prefix — see our Kong API gateway guide
  • Teams that publish OpenAPI specs per major version

On a legal-tech client portal I built, path versioning let the mobile team stay on /v1/ while the web dashboard moved to /v2/ with expanded document metadata. Logs made support tickets easy to triage.

How Does Header-Based API Versioning Work?

Header-based versioning keeps URLs stable. The client sends an Accept header or a custom header like Api-Version: 2. The server selects the response shape through content negotiation defined in RFC 7231 section 5.3.2.

Header Versioning Request FlowHTTP ClientMiddlewareVersionResolverControllerGET /api/usersParse Accept headerBind v2 handlerAccept: application/vnd.myapp.v2+jsonFallback: application/json maps to v1406 if no matching representation
Header-based API versioning: middleware resolves the contract before the controller runs

Custom header middleware in Laravel

// app/Http/Middleware/ResolveApiVersion.php
public function handle(Request $request, Closure $next): Response
{
    $version = $request->header('Api-Version', '1');
    $request->attributes->set('api_version', $version);

    return $next($request);
}
// app/Http/Controllers/Api/UserController.php
public function show(Request $request, User $user): JsonResponse
{
    return match ($request->attributes->get('api_version')) {
        '2' => UserResourceV2::make($user)->response(),
        default => UserResourceV1::make($user)->response(),
    };
}

Vendor media types follow the pattern application/vnd.{vendor}.{version}+json. GitHub's REST API uses this approach. Clients must set headers correctly — a common support burden when integrators test from browser address bars.

Header versioning suits B2B APIs where every client runs a maintained SDK. It fails fast when casual integrators paste URLs into Postman without configuring headers. Pair it with clear docs — our post on API documentation with Scribe for Laravel covers generating examples per version.

Which API Versioning Strategy Fits Laravel 13 Best?

Laravel 13 on PHP 8.3+ gives you clean route groups, middleware, API Resources, and Form Requests. The framework does not mandate a versioning style — your contract design drives the choice.

API Versioning Decision TreePublic REST API?Yes: URI /v1/path prefixInternal only?YesNoSDK-owned?Header versioningURL must stay fixed?Query or custom headerSeparate deploy?Subdomain v2.apiNever mix two strategies on one resource
Decision tree when API versioning strategies compared against your audience and infrastructure

Practical Laravel 13 layout

  1. Pick one primary strategy per API surface — usually URI path.
  2. Namespace controllers: App\Http\Controllers\Api\V1 and V2.
  3. Publish separate OpenAPI documents per major version.
  4. Apply rate limiting per version if v1 clients behave differently from v2.
  5. Authenticate with Sanctum or Passport — tokens are version-agnostic unless you scope them.
  6. Write contract tests — Pact contract testing catches breaking diffs before deploy.

For Symfony 8.1 projects, the same principles apply via route prefixes and serializer groups. Symfony's serializer groups map cleanly to version-specific output shapes — see Symfony serializer for API responses.

Query-parameter versioning (?version=2) works for feature flags and beta endpoints. I use it sparingly on admin-only routes where the audience is one frontend team we control. Public APIs on platforms like Quick And Easy Nepalese Grocery stick to path prefixes because delivery-partner integrations need obvious URLs.

Version numbering rules

Increment the major version only on breaking changes. Minor and patch numbers belong in changelogs and response headers — not necessarily in the URL. A practical response header:

X-API-Version: 2.3.1
Sunset: Sat, 01 Mar 2027 00:00:00 GMT
Deprecation: true
Link: <https://docs.example.com/migrate-v2-to-v3>; rel="deprecation"

Stripe pins dated API versions per account — a different model worth studying in their official versioning documentation. Account-level pinning shifts migration cost to the provider; URL major versions shift it to the client.

How Do Query Parameter and Subdomain Versioning Compare?

Query-parameter versioning appends ?version=2 or ?api-version=2 to an unchanged path. Proxies may strip query strings from cache keys incorrectly if not configured — test CDN behaviour before production.

// routes/api.php — query param fallback (internal APIs only)
Route::get('/reports/summary', function (Request $request) {
    $version = $request->query('version', '1');

    return match ($version) {
        '2' => app(SummaryControllerV2::class)->index(),
        default => app(SummaryControllerV1::class)->index(),
    };
});

Subdomain versioning (v2.api.example.com) suits organisations running separate deploy units per version. DNS, TLS certificates, and CORS policies multiply. I've seen this on large microservice estates described in our monolith-to-microservices migration guide — not typical for a Laravel monolith on a single VPS.

How Do You Deprecate and Sunset Old API Versions Safely?

Versioning without a deprecation plan creates zombie endpoints nobody dares remove. Define a published policy: minimum notice period, sunset headers, and a hard removal date.

API Deprecation Lifecyclev2 Releasedv1 maintainedDeprecationSunset header setNotice Period6–12 months typicalSunsetv1 returns 410Operational ChecklistLog v1 traffic weekly — identify active API keysEmail integrators 90 / 30 / 7 days before sunsetReturn 410 Gone with migration doc link after cutoff
Safe API version sunset: announce early, monitor traffic, remove only when usage drops

Follow the full playbook in API deprecation and sunset best practices. Key steps I repeat on every project:

  • Log requests by version and client ID — know who still calls v1.
  • Add Sunset and Deprecation response headers months before removal.
  • Publish a migration guide with field mapping tables — use our JSON formatter in examples so diffs are readable.
  • Never reuse version numbers after sunset — v3 follows v2, not a recycled v1.
  • Run Pact or Postman collections in CI so v2 parity is proven — see API testing with Postman and Newman.

On payment integrations — eSewa, Khalti, Stripe — webhook payloads often cannot break silently. Version webhook handlers separately and accept both formats during transition. Our OpenAI API integration guide shows the same pattern for upstream API drift.

If you publish an SDK, version it alongside the API — guidance in SDK design for your public API. Mijar Law Associates' client portal kept v1 mobile endpoints alive for nine months while law firms migrated document upload flows to v2.

What Mistakes Break API Versioning in Production?

These failures show up repeatedly across client projects and are cheap to avoid at design time.

Mixing URI and header versioning on the same resource confuses clients and breaks cache rules — pick one primary signal.

  • Leaking v2 fields into v1 responses — use separate API Resource classes, not conditional branches scattered in controllers.
  • Breaking changes without a major bump — renaming phone to mobile is breaking even if the database column changed months ago.
  • No default version — undocumented defaults become undeletable. Document whether bare /api/users maps to v1 or returns 400.
  • Forgetting gateway config — WAF rules, CORS, and rate limits must include new prefixes. Read Kong vs Traefik vs AWS API Gateway for edge routing patterns.
  • Skipping changelog discipline — treat API changelogs like database migrations; tie each entry to a version bump.

For long-lived platforms, schedule version reviews during support and maintenance retainers. Laravel 11 reached EOL in March 2026 — upgrading to Laravel 12 or 13 is a good moment to audit which API versions still earn their keep.

Key Takeaways

  • URI path versioning (/api/v1/) is the best default for public REST APIs — visible in logs, docs, and SDKs.
  • Header and vendor media-type versioning fits SDK-controlled B2B clients when URLs must stay constant.
  • Increment major versions only on breaking changes; publish Sunset headers at least six months before removal.
  • Separate Laravel controllers, Form Requests, and API Resources per version — never share serialization logic.
  • Run contract tests in CI and monitor per-version traffic before sunsetting old endpoints.
  • Document one strategy per API surface; mixing path and header signals creates support and caching bugs.

People Also Ask

What is the most common API versioning strategy?

URI path versioning is the most widely used approach for public REST APIs. Major vendors expose /v1/ and /v2/ path segments because they are easy to document, debug in access logs, and configure at the gateway layer without custom header parsing.

Should API versioning go in the URL or header?

Use the URL for public APIs with diverse clients — mobile apps, partner integrations, no-code tools. Use headers when every consumer runs your SDK and you need a stable URL for caching or legacy compatibility. Headers add friction for manual testing but keep paths clean.

How long should you support deprecated API versions?

Most production teams allow six to twelve months between deprecation announcement and hard removal. Monitor active traffic throughout; if enterprise clients still call the old version, extend the window rather than breaking payroll, payment, or compliance workflows without notice.

Does GraphQL need the same versioning as REST?

GraphQL APIs often favour additive schema evolution and field deprecation directives instead of URL major versions. REST's resource-oriented contracts map more naturally to explicit /v1/ and /v2/ boundaries — especially when external clients cache full response shapes.

Pick a Strategy and Document It Today

API versioning strategies compared on real criteria — client friction, cache behaviour, gateway fit, deprecation cost — point most Laravel and Symfony teams toward URI path prefixes for public surfaces. Lock the choice in your OpenAPI spec, enforce it in code review, and plan sunset headers before v2 ships. Need help designing a versioned API for a portal, marketplace, or payment integration? Contact us or explore API development in Nepal — and browse the Mijar Law Associates portfolio for a client portal that runs versioned document APIs in production.

Frequently Asked Questions

URI path versioning is the most widely used approach for public REST APIs. Major vendors expose /v1/ and /v2/ path segments because they are easy to document, debug in access logs, and configure at the gateway layer without custom header parsing.

Use the URL for public APIs with diverse clients — mobile apps, partner integrations, no-code tools. Use headers when every consumer runs your SDK and you need a stable URL for caching or legacy compatibility. Headers add friction for manual testing but keep paths clean.

Most production teams allow six to twelve months between deprecation announcement and hard removal. Monitor active traffic throughout; if enterprise clients still call the old version, extend the window rather than breaking payroll, payment, or compliance workflows without notice.

URI path versioning embeds the major version in the URL segment, so clients call /api/v1/users today and migrate to /api/v2/users when ready. It wins for public partner APIs, mobile apps that hard-code base URLs, and API gateways that rate-limit by path prefix. Proxies, CDNs, and WAF rules can route or block by path prefix without parsing headers. In Laravel 13, group versioned routes under a prefix with separate controller directories for v1 and v2 so refactors stay readable.

Header-based versioning keeps URLs stable while the client sends an Accept header or a custom header like Api-Version: 2. Middleware resolves the contract before the controller runs, storing the version on the request attributes. The controller then returns the matching API Resource shape. Vendor media types follow application/vnd.{vendor}.{version}+json, as GitHub's REST API does. This suits B2B APIs where every client runs a maintained SDK, but fails when casual integrators test from browser address bars without configuring headers.

For Laravel 13 on PHP 8.3+, URI path versioning is the default I'd recommend for public surfaces. Group routes with Route::prefix('v1') and Route::prefix('v2'), namespace controllers under App\Http\Controllers\Api\V1 and V2, and publish separate OpenAPI documents per major version. Use separate Form Requests and API Resources per version — sharing serialization logic creates hidden coupling. Authenticate with Sanctum or Passport since tokens are version-agnostic unless scoped. Query-parameter versioning works sparingly on admin-only routes your team controls.

Breaking changes include removing fields, renaming properties, changing validation rules, or altering HTTP semantics — renaming phone to mobile is breaking even if the database column changed months ago. Non-breaking additions such as new optional fields or new endpoints typically stay on the current version. Increment the major version only on breaking changes. Minor and patch numbers belong in changelogs and response headers like X-API-Version: 2.3.1, not necessarily in the URL path itself.

Query-parameter versioning appends ?version=2 or ?api-version=2 to an unchanged path, sitting between URI paths and headers on client friction and cache behaviour. It works for internal tools, gradual rollout behind flags, and admin-only routes where one frontend team is the audience. Proxies may strip query strings from cache keys incorrectly if not configured, so test CDN behaviour before production. Public APIs on platforms like Quick And Easy Nepalese Grocery stick to path prefixes because delivery-partner integrations need obvious URLs.

Subdomain versioning uses addresses like v2.api.example.com and suits organisations running separate deploy units per version. It offers high cache-friendliness but high client friction because DNS, TLS certificates, and CORS policies multiply. I've seen this on large microservice estates, not typical for a Laravel monolith on a single VPS. For most Laravel and Symfony teams shipping from one server, URI path prefixes deliver similar clarity with far less infrastructure overhead.

Define a published policy with a minimum notice period, sunset headers, and a hard removal date. Log requests by version and client ID to know who still calls v1. Add Sunset and Deprecation response headers months before removal, plus a Link header pointing to a migration guide with field mapping tables. Never reuse version numbers after sunset. Run Pact or Postman collections in CI so v2 parity is proven. On payment integrations with eSewa, Khalti, or Stripe, version webhook handlers separately and accept both formats during transition.

Mixing URI and header versioning on the same resource confuses clients and breaks cache rules — pick one primary signal per API surface. Leaking v2 fields into v1 responses happens when you share API Resource classes instead of keeping versions separate. Breaking changes without a major bump, undocumented default versions, and forgotten gateway config for new path prefixes are equally common. Skipping changelog discipline and treating API changes casually creates zombie endpoints nobody dares remove months later.

GraphQL APIs often favour additive schema evolution and field deprecation directives instead of URL major versions. REST's resource-oriented contracts map more naturally to explicit /v1/ and /v2/ boundaries, especially when external clients cache full response shapes. If your integrators copy URLs from documentation, hard version boundaries in the path are easier to reason about than schema-level deprecation alone. Pick the model that matches how your clients discover and pin contracts.

Vendor media types follow the pattern application/vnd.{vendor}.{version}+json and are selected through content negotiation defined in RFC 7231 section 5.3.2. GitHub's REST API uses this approach instead of path segments. The server picks the response shape based on the Accept header before the controller serializes output. This fits strict hypermedia APIs and SDK-controlled B2B clients, but requires integrators to set headers correctly — a common support burden when someone pastes a URL into Postman without configuring Accept.

Keep v1 and v2 in separate controller directories such as App\Http\Controllers\Api\V1 and V2. Use separate Form Requests and API Resources per version — never share a single Resource class between v1 and v2 because a field rename in v2 breaks v1 serialization silently. Avoid conditional branches scattered in controllers. On a legal-tech client portal I built, path versioning let the mobile team stay on /v1/ while the web dashboard moved to /v2/ with expanded document metadata, and logs made support tickets easy to triage.

Publish practical headers alongside your sunset date: X-API-Version: 2.3.1 for the current contract, Sunset: Sat, 01 Mar 2027 00:00:00 GMT for the hard removal date, Deprecation: true to signal the version is winding down, and Link: https://docs.example.com/migrate-v2-to-v3; rel="deprecation" pointing to your migration guide. Announce at least six months before removal. Mijar Law Associates' client portal kept v1 mobile endpoints alive for nine months while law firms migrated document upload flows to v2 — monitor traffic and extend if clients still depend on the old contract.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: