Skip to content

Version Lifecycle

Jean-Marc Strauven edited this page Dec 23, 2025 · 1 revision

Version Lifecycle

Learn how to manage the complete lifecycle of your API versions from active to sunset.


Lifecycle States

Every API version goes through a predictable lifecycle:

┌──────────┐    ┌────────────┐    ┌─────────┐
│  Active  │ → │ Deprecated │ → │ Sunset  │
└──────────┘    └────────────┘    └─────────┘
     ↑
┌──────────┐
│   Beta   │
└──────────┘

Version States

Active

The current stable version. This is the default state for new versions.

ApiRoute::version('v2', function () {
    Route::apiResource('users', UserController::class);
})->current();

Characteristics:

  • Full support and maintenance
  • No deprecation headers
  • Recommended for production use

Response headers:

X-API-Version: v2
X-API-Version-Status: active

Beta

A preview version for testing new features.

ApiRoute::version('v3', function () {
    Route::apiResource('users', UserController::class);
})->beta();

Characteristics:

  • May have breaking changes
  • Not recommended for production
  • Used for early adopter testing

Response headers:

X-API-Version: v3
X-API-Version-Status: beta

Deprecated

A version that is still functional but should be migrated away from.

ApiRoute::version('v1', function () {
    Route::apiResource('users', UserController::class);
})->deprecated('2025-06-01');

Characteristics:

  • Still fully functional
  • Deprecation headers included
  • Limited or no new features
  • Migration encouraged

Response headers:

X-API-Version: v1
X-API-Version-Status: deprecated
Deprecation: Sun, 01 Jun 2025 00:00:00 GMT

Sunset

A version that has reached end-of-life.

ApiRoute::version('v1', function () {
    Route::apiResource('users', UserController::class);
})
->deprecated('2025-06-01')
->sunset('2025-12-01');  // Past date triggers sunset

Characteristics:

  • No longer available (if configured to reject)
  • Returns error response
  • Clients must upgrade

Response (if action = 'reject'):

{
    "error": "api_version_sunset",
    "message": "API version v1 is no longer available.",
    "sunset_date": "2025-12-01T00:00:00+00:00",
    "successor": "v2",
    "migration_guide": "https://docs.example.com/migration/v1-to-v2"
}

Lifecycle Timeline Example

2024-01                                    2025-06-01           2025-12-01
    │                                           │                    │
    │←───────── Active ─────────────────────────│←─── Deprecated ───→│←── Sunset
    │                                           │                    │
    v                                           v                    v
  v1 released                              v1 deprecated         v1 removed
  v2 in development                        v2 becomes current    v3 released

Managing Transitions

Deprecating a Version

Using the Artisan command:

php artisan api:deprecate v1 --on=2025-06-01 --sunset=2025-12-01

Or programmatically:

ApiRoute::version('v1', function () {
    // ...
})
->deprecated('2025-06-01')
->sunset('2025-12-01')
->setSuccessor('v2');

Sunsetting a Version

Using the Artisan command:

php artisan api:sunset v1

Or by setting a past sunset date:

->sunset('2024-01-01')  // Past date = immediately sunset

Sunset Behavior

Configure how sunset versions are handled:

// config/apiroute.php
'sunset' => [
    'action' => 'reject',      // 'reject', 'warn', 'allow'
    'status_code' => 410,      // HTTP Gone
    'include_migration_url' => true,
],

Action Options

Action Behavior
reject Return error response (default)
warn Allow request but add warning headers
allow Allow request without warning

HTTP Status Codes

Code Meaning
410 Gone (recommended)
404 Not Found
426 Upgrade Required

Sunset Response

When action = 'reject':

{
    "error": "api_version_sunset",
    "message": "API version v1 is no longer available.",
    "sunset_date": "2025-12-01T00:00:00+00:00",
    "successor": "v2",
    "migration_guide": "https://docs.example.com/migration/v1-to-v2"
}

Configure migration guides:

// config/apiroute.php
'documentation' => [
    'migration_guides' => [
        'v1' => 'https://docs.example.com/api/migration/v1-to-v2',
        'v2' => 'https://docs.example.com/api/migration/v2-to-v3',
    ],
],

Status Checking

In Controllers

use Grazulex\ApiRoute\Facades\ApiRoute;

class UserController extends Controller
{
    public function index(Request $request)
    {
        if ($request->isDeprecatedVersion()) {
            // Log usage of deprecated version
            Log::info('Deprecated version used', [
                'version' => $request->apiVersion(),
                'user' => $request->user()?->id,
            ]);
        }

        // ...
    }
}

Via Facade

use Grazulex\ApiRoute\Facades\ApiRoute;

// Check version status
if (ApiRoute::isDeprecated('v1')) {
    // Version is deprecated
}

if (ApiRoute::isSunset('v1')) {
    // Version is sunset
}

if (ApiRoute::isActive('v2')) {
    // Version is active
}

Via VersionDefinition

$version = ApiRoute::getVersion('v1');

$version->isActive();      // bool
$version->isBeta();        // bool
$version->isDeprecated();  // bool
$version->isSunset();      // bool
$version->isUsable();      // bool (not sunset)

Events

Listen to lifecycle events:

use Grazulex\ApiRoute\Events\DeprecatedVersionAccessed;
use Grazulex\ApiRoute\Events\VersionDeprecated;
use Grazulex\ApiRoute\Events\VersionSunset;

// In EventServiceProvider
protected $listen = [
    DeprecatedVersionAccessed::class => [
        LogDeprecatedVersionUsage::class,
    ],
    VersionDeprecated::class => [
        NotifyApiConsumers::class,
    ],
    VersionSunset::class => [
        ArchiveVersionResources::class,
    ],
];

See Events for more details.


Best Practices

1. Communicate Early

Announce deprecation well in advance (3-6 months minimum).

2. Provide Migration Guides

->setSuccessor('v2')
->documentation('https://docs.example.com/migration/v1-to-v2')

3. Monitor Usage

php artisan api:stats --period=30

4. Gradual Transition

Consider using the warn action before reject:

// Phase 1: Warn users
'sunset' => ['action' => 'warn'],

// Phase 2: Reject requests
'sunset' => ['action' => 'reject'],

5. Use Rate Limiting

Encourage migration with lower rate limits:

ApiRoute::version('v1', fn() => ...)
    ->deprecated('2025-06-01')
    ->rateLimit(100);  // Lower limit

ApiRoute::version('v2', fn() => ...)
    ->current()
    ->rateLimit(1000); // Higher limit

Next Steps

Clone this wiki locally