Skip to content
Back to skills

Api Versioning 2

ASecurity

API versioning strategies including URL-based, header-based, and breaking change management

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 27, 2026
businessapidatabasebackenddocumentation

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 27, 2026

npx -y skills add David-Li0406/meta-skill-evloving --skill api-versioning-2 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Api Versioning 2?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Api Versioning 2
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/david-li0406-api-versioning-2/badge)](https://www.skillsdirectory.com/skills/david-li0406-api-versioning-2)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: api-versioning
description: API versioning strategies including URL-based, header-based, and breaking change management
---

# API Versioning Skill

Use this skill when:
- Designing API versioning strategies
- Managing breaking changes
- Creating deprecation workflows
- Implementing API gateways
- Designing backward-compatible APIs
- Planning multi-version support

## When to Use

### Use this skill when:
- Your API needs to evolve without breaking existing clients
- You're planning breaking changes to API contracts
- Designing multi-version support
- Implementing API deprecation strategies
- Creating API gateway patterns

### Key Scenarios

1. **Major API Changes**: Breaking changes to existing contracts
2. **Feature Additions**: New endpoints or parameters
3. **Field Modifications**: Changing field types or structures
4. **Deprecation**: Removing deprecated endpoints
5. **Gateway Integration**: Proxying multiple versions

---

## Versioning Strategies

### 1. URI Path Versioning

```elixir
# config/router.ex
defmodule MyAppWeb.Router do
  use MyAppWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :put_secure_browser_headers
    plug :put_root_layout, html: {MyAppWeb.Layouts, :root}
    plug :protect_from_forgery
    plug :put_user_token
  end

  # v1 APIs
  scope "/api/v1", as: :api do
    pipe_through :api do
      get "/users", UserController, :index
      post "/users", UserController, :create
      get "/users/:id", UserController, :show
      put "/users/:id", UserController, :update
      delete "/users/:id", UserController, :delete
    end

  # v2 APIs (breaking changes)
  scope "/api/v2", as: :api do
    pipe_through :api do
      get "/users", UserV2Controller, :index
      post "/users", UserV2Controller, :create
      get "/users/:id", UserV2Controller, :show
      put "/users/:id", UserV2Controller, :update
      delete "/users/:id", UserV2Controller, :delete)
    end
  end
end
```

### 2. Header-Based Versioning

```elixir
# config/config.exs
config :my_app, MyAppWeb.Gateway,
  api_version: "v2"

defmodule MyAppWeb.Gateway do
  def call(conn, opts) do
    # Accept version from header
    case get_header(conn, "api-version", nil) do
      nil -> {:ok, conn}
      version when is_binary(version) ->
        Logger.info("API version from header: #{version}")
        {:ok, put_private(conn, :api_version, version)}
    end
    end
end

# In controller
defmodule UserV2Controller do
  alias MyAppWeb.Gateway

  def index(conn, params) do
    version = get_api_version(conn)

    case version do
      nil -> 
      # No version specified, use latest
      list_users_v2(conn, params)
      
      "v1" ->
        # Forward to v1 controller for backward compatibility
        UserV1Controller.index(conn, params)
      
      "v2" ->
        list_users_v2(conn, params)
      
      _ ->
        conn
        |> put_status(400)
        |> json(%{error: "Unsupported API version: #{version}"})
    end
  end

  defp get_api_version(conn) do
    Map.get(conn.private, :api_version, "v2")
  end
end
```

---

## Backward Compatibility

### 1. Deprecation Warnings

```elixir
defmodule MyApp.Deprecation do
  use GenServer
  require Logger

  # Client API
  def start_link(opts), do: GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  def deprecate(feature_name, deadline_date), do: GenServer.cast(__MODULE__, {:deprecate, feature_name, deadline_date})
  def is_deprecated?(feature_name), do: GenServer.call(__MODULE__, {:is_deprecated, feature_name})
  def get_deprecation_info(feature_name), do: GenServer.call(__MODULE__, :get_deprecation_info, feature_name})

  @impl true
  def init(opts), do
    Logger.info("Starting deprecation service")
    {:ok, %{
      deprecated_features: %{},
      opts: opts
    }}
  end

  @impl true
  def handle_cast({:deprecate, feature_name, deadline_date}, state) do
    new_deprecated = %{
      feature_name: %{
        deprecated_at: DateTime.utc_now(),
        deadline_date: deadline_date
      }
    }

    new_state = put_in(state, :deprecated_features, feature_name, new_deprecated)
    {:noreply, new_state}
  end

  @impl true
  def handle_call({:is_deprecated, feature_name}, _from, state) do
    case Map.get(state.deprecated_features, feature_name) do
      nil ->
        {:reply, false, state}

      %{deprecated_at: deadline_date} ->
        now = DateTime.utc_now()
        if DateTime.compare(now, deadline_date) == :gt do
          Logger.error("Feature #{feature_name} is deprecated and past deadline")
          {:reply, true, state}
        else
          {:reply, false, state}
        end

      _deprecated_info ->
        {:reply, true, state}
    end
  end

  def handle_call({:get_deprecation_info, feature_name}, _from, state) do
    case Map.get(state.deprecated_features, feature_name) do
      nil ->
        {:reply, nil, state}

      info -> {:reply, info, state}
    end
  end
end
```

### 2. Field Evolution

```elixir
# Schema evolution with Ash
defmodule MyApp.Accounts.User do
  use Ash.Resource

  attributes do
    uuid_primary_key :id
    attribute :email, :string, allow_nil?: false
    attribute :name, :string
    # Deprecated field - marked for removal
    attribute :old_field, :string, deprecated_reason: "Replaced by new_field", allow_nil?: true
    attribute :new_field, :string

    timestamps()
  end

  actions do
    read :read
    update :update
    create :create
    destroy :destroy
  end
end

# Migration for deprecation
defmodule MyApp.Repo.Migrations.DeprecateOldField do
  use Ecto.Migration

  def change do
    alter table(:users) do
      add :new_field, :string
    end

    # Add deprecation warning to queries
    create index(:users, [:email, :new_field])
  end

  def down do
    drop index(:users, [:email, :new_field])
    alter table(:users) do
      remove :new_field
    end
  end
end
```

### 3. Adapters for Backward Compatibility

```elixir
defmodule MyApp.Api.V1ToV2Adapter do
  require Logger

  def adapt_request(conn, params) do
    Logger.info("Adapting v1 request to v2 structure")

    # Map v1 params to v2 structure
    v2_params = %{
      "user_id" => params["id"],
      "email" => params["email"],
      "password" => params["password"],
      # New required fields
      "full_name" => params["full_name"] || params["name"],
      "phone" => params["phone"]
    }

    Logger.debug("Mapped v1 params to v2: #{inspect(v2_params)}")

    {:ok, v2_params}
  end

  def adapt_response(v2_response) do
    Logger.info("Adapting v2 response to v1 structure")

    # Map v2 response to v1 structure
    v1_response = %{
      "id" => v2_response.id,
      "email" => v2_response.email,
      "name" => v2_response.full_name || v2_response.name,
      "phone" => v2_response.phone
      "created_at" => v2_response.inserted_at
    }

    Logger.debug("Mapped v2 response to v1: #{inspect(v1_response)}")

    {:ok, v1_response}
  end
end
```

---

## Breaking Change Management

### 1. Semanic Versioning

```elixir
defmodule MyApp.Versioning do
  @major "1.0"
  @minor "0"
  @patch "0"
  @pre_release "dev"

  def current_version do
    @major <> "." <> @minor <> "." <> @patch
  end

  def next_major_version, do
    next_major = String.to_integer(@major) + 1)
    %{current_version | major: Integer.to_string(next_major), minor: @minor, patch: @patch}
  end

  def next_minor_version, do
    next_minor = String.to_integer(@minor) + 1)
    %{current_version | major: @major, minor: Integer.to_string(next_minor), patch: @patch}
  end

  def next_patch_version, do
    next_patch = String.to_integer(@patch) + 1)
    %{current_version | major: @major, minor: @minor, patch: Integer.to_string(next_patch)}
  end

  def bump_version(version_type) do
    case version_type do
      :major -> next_major_version()
      :minor -> next_minor_version()
      :patch -> next_patch_version()
      :pre -> current_version()
    end
  end

  def is_backward_compatible?(v1, v2) do
    # Parse versions
    v1_parts = String.split(v1, ".")
    v2_parts = String.split(v2, ".")

    {v1_major, v1_minor, _v1_patch} = v1_parts
    {v2_major, v2_minor, _v2_patch} = v2_parts

    # v1 < v2: backward compatible
    cond do
      String.to_integer(v1_major) < String.to_integer(v2_major) -> true
      String.to_integer(v1_major) == String.to_integer(v2_major) ->
        String.to_integer(v1_minor) < String.to_integer(v2_minor) -> true
        true -> false
    end
  end
  end
end
```

### 2. Breaking Change Policy

```elixir
defmodule MyApp.Api.BreakingChangePolicy do
  require Logger

  # Breaking change levels
  @levels [:minor, :major, :breaking]
  @policy %{
    :minor => %{
      deprecation_period: 90,  # 90 days
      warnings: true,
      client_notification: true,
      grace_period: 7   # 7 days
    },
    :major => %{
      deprecation_period: 180,  # 180 days
      warnings: true,
      client_notification: true,
      grace_period: 30  # 30 days
      grace_period: 7  # 7 days
    },
    :breaking => %{
      deprecation_period: 0,
      warnings: true,
      client_notification: true,
      grace_period: 0
      client_notification: true,
      requires_major_version_bump: true
    }
  }

  # Client API
  def check_compatibility(version, feature), do
    # Get feature version from config
    feature_versions = Application.get_env(:my_app, :feature_versions, %{})

    case Map.get(feature_versions, feature) do
      nil -> {:ok, :compatible}
      feature_version -> is_backward_compatible?(version, feature_version)
    end
  end

  def get_deprecation_deadline(feature, change_level) do
    policy = Map.get(@policy, change_level, @policy[:minor])
    days = policy[:deprecation_period]
    Date.add(Date.utc_today(), days)
  end

  # Notify clients
  def notify_deprecation(feature, deadline, policy, change_level) do
    if policy[:client_notification] do
      Logger.warning("Deprecating #{feature}, deadline: #{deadline}")
      # Send notification to clients
      MyApp.Notifier.notify_clients(:feature_deprecation, %{
        feature: feature,
        deadline: deadline,
        change_level: change_level
      })
    end
  end
end
```

---

## API Gateway Patterns

### 1. Version-Based Routing

```elixir
defmodule MyApp.Gateway.Router do
  use Plug.Router

  @api_versions ["v1", "v2", "v3"]

  plug Plug.Static, at: "/", from: :v1
  plug Plug.Static, at: "/v1", from: :v2
  plug Plug.Static, at: "/v2", from: :v3

  # Default version (latest)
  @api_version "v3"

  # Health check endpoint
  get "/health", HealthCheckController, :check

  # API version endpoints (with latest alias)
  scope "/", as: :api do
    pipe_through :api do
      # Latest version routes (aliased to /v3)
      get "/users", UserV3Controller, :index
      post "/users", UserV3Controller, :create
    end
  end

  # Versioned endpoints
  Enum.each(@api_versions, fn version ->
    scope "/#{version}", as: :api do
      pipe_through :api do
        get "/users", :"UserController#{version}", :index
        post "/users", :"UserController#{version}", :create
      end
    end)
  end
end
```

### 2. Version Compatibility Headers

```elixir
defmodule MyApp.VersionHeaders do
  @supported_versions ["v1", "v1.1", "v2", "v2.1", "v3"]

  def validate_version(conn) do
    case get_header(conn, "api-version", nil) do
      nil ->
        {:ok, nil}
      version ->
        if version in @supported_versions do
          {:ok, version}
        else
          {:error, :unsupported_version}
        end
    end
  end

  def add_version_headers(conn) do
    supported_versions = Enum.join(@supported_versions, ", ")
    conn
    |> put_resp_header("api-version", @api_version)
    |> put_resp_header("api-supported-versions", supported_versions)
    |> put_resp_header("api-latest-version", @api_version)
  end
end
```

---

## Best Practices

### DO

✅ Choose versioning strategy upfront
✅ Document version semantics clearly
✅ Provide deprecation warnings
✅ Implement graceful degradation
✅ Use semantic versioning
✅ Test backward compatibility
✅ Document breaking changes in CHANGELOG
✅ Notify clients of deprecations
✅ Support multiple versions when needed
✅ Consider API gateways for evolution

### DON'T

❌ Change API versioning strategy mid-project
❌ Forget to document breaking changes
❌ Remove v1 immediately without deprecation
❌ Skip deprecation warnings
❌ Make breaking changes without grace period
❌ Ignore backward compatibility
❌ Forget to version databases
❌ Use ambiguous version numbers
❌ Skip client notification
❌ Make breaking changes without documentation

---

## Integration with ai-rules

### Roles to Reference

- **Architect**: Use for API design and versioning strategy selection
- **Orchestrator**: Implement versioning in features
- **Backend Specialist**: Design version-aware APIs
- **Reviewer**: Verify version compatibility and deprecation policies
- **QA**: Test backward compatibility

### Skills to Reference

- **api-design**: Use versioning in API design
- **test-generation**: Write tests for version compatibility
- **observability**: Monitor deprecated API usage
- **distributed-systems**: Combine with distributed API patterns

---

## Summary

API versioning provides:
- ✅ URI path versioning
- ✅ Header-based versioning
- ✅ Breaking change management
- ✅ Deprecation workflows
- ✅ Backward compatibility patterns
- ✅ Multi-version support

**Key**: Choose strategy upfront, document changes, deprecate gracefully, and maintain backward compatibility.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…