Isaac.

API Versioning Strategies

Design APIs that evolve while maintaining backward compatibility.

By EMEPublished: February 20, 2025
api versioningcompatibilityevolutionbackward compatibility

A Simple Analogy

API versioning is like a company's product line. As products improve, you maintain older versions for existing customers while offering newer versions. You can't force everyone to upgrade simultaneously.


Why Version APIs?

  • Evolution: APIs must change to add features
  • Compatibility: Existing clients shouldn't break
  • Gradual migration: Time for clients to upgrade
  • Support: Maintain old versions for existing users
  • Flexibility: Different clients use different versions

Versioning Strategies

URL Path Versioning

GET /api/v1/users        # Version 1
GET /api/v2/users        # Version 2
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public class UsersController : ControllerBase
{
    [HttpGet]
    public IActionResult GetUsers() => Ok(new[] { "v1" });
}

Query Parameter Versioning

GET /api/users?version=1
GET /api/users?version=2
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    [HttpGet]
    public IActionResult GetUsers([FromQuery] int version = 1)
    {
        return version switch
        {
            1 => Ok(new[] { "name", "email" }),
            2 => Ok(new[] { "id", "name", "email", "phone" }),
            _ => BadRequest("Unsupported version")
        };
    }
}

Header Versioning

GET /api/users
X-API-Version: 1
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    [HttpGet]
    public IActionResult GetUsers([FromHeader(Name = "X-API-Version")] int version = 1)
    {
        return version switch
        {
            1 => Ok(GetUsersV1()),
            2 => Ok(GetUsersV2()),
            _ => BadRequest("Unsupported version")
        };
    }
}

Media Type Versioning

GET /api/users
Accept: application/vnd.company.user-v1+json

Deprecation Strategy

[Obsolete("Use v2 instead")]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class UsersControllerV1 : ControllerBase
{
    [HttpGet]
    public IActionResult GetUsers()
    {
        return Ok(new[] { new { Name = "Alice" } });
    }
}

[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class UsersControllerV2 : ControllerBase
{
    [HttpGet]
    public IActionResult GetUsers()
    {
        return Ok(new[] { 
            new { Id = 1, Name = "Alice", Email = "alice@example.com" } 
        });
    }
}

Practical Example

// V1: Simple user response
public class UserDtoV1
{
    public string Name { get; set; }
    public string Email { get; set; }
}

// V2: Extended with additional fields
public class UserDtoV2
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public DateTime CreatedAt { get; set; }
    public string Status { get; set; }
}

[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public class UsersController : ControllerBase
{
    [HttpGet("{id}")]
    [MapToApiVersion("1.0")]
    public async Task<ActionResult<UserDtoV1>> GetUserV1(int id)
    {
        var user = await _userService.GetUserAsync(id);
        return Ok(new UserDtoV1 
        { 
            Name = user.Name, 
            Email = user.Email 
        });
    }
    
    [HttpGet("{id}")]
    [MapToApiVersion("2.0")]
    public async Task<ActionResult<UserDtoV2>> GetUserV2(int id)
    {
        var user = await _userService.GetUserAsync(id);
        return Ok(new UserDtoV2 
        { 
            Id = user.Id,
            Name = user.Name, 
            Email = user.Email,
            CreatedAt = user.CreatedAt,
            Status = user.Status
        });
    }
}

Sunset Policy

Version 1: Active until Dec 31, 2024
Version 2: Supported for 2 years after release
Version 3: Current stable version

Timeline:
- V1: Deprecated (Oct 2024), Sunset (Dec 31, 2024)
- V2: Stable, 18 months remaining
- V3: Current, fully supported

Best Practices

  1. Plan ahead: Design versioning from day one
  2. Semantic versioning: Major.Minor.Patch
  3. Deprecation warnings: Notify clients early
  4. Sunset timeline: Clear migration path
  5. Documentation: Document all versions

Related Concepts to Explore

  • API gateway versioning
  • Content negotiation
  • Breaking vs. non-breaking changes
  • Schema evolution
  • Backward compatibility testing

Summary

API versioning enables controlled evolution while maintaining backward compatibility. Choose a strategy, communicate clearly, and provide migration paths for smooth client upgrades.