diff --git a/lso/routes/ip_trunk.py b/lso/routes/ip_trunk.py
index e12c7fff2d3ba87b5c95968696146955930b8197..ae3b2218b5e9a1ef255c02b0a070285739d2916e 100644
--- a/lso/routes/ip_trunk.py
+++ b/lso/routes/ip_trunk.py
@@ -42,6 +42,18 @@ class IPTrunkModifyParams(IPTrunkParams):
     old_subscription: dict
 
 
+class IPTrunkMigrationParams(IPTrunkParams):
+    """Additional parameters for migrating an IPTrunk."""
+
+    #: Whether this playbook execution should be a dry run, or run for real. Defaults to ``True`` for obvious reasons,
+    #: also making it an optional parameter.
+    dry_run: Optional[bool] = True
+    #: The new Router that this IP Trunk is migrating to.
+    new_side: dict
+    #: An Ansible playbook verb that is passed along for indicating the phase of the migration that is performed.
+    verb: str
+
+
 class IPTrunkCheckParams(IPTrunkParams):
     """Additional parameters for checking an IPtrunk."""
 
@@ -173,7 +185,38 @@ def check_ip_trunk(params: IPTrunkCheckParams) -> PlaybookLaunchResponse:
 
     return run_playbook(
         playbook_path=path.join(config_params.ansible_playbooks_root_dir, "iptrunks_checks.yaml"),
-        inventory=params.subscription["iptrunk"]["iptrunk_sideA_node"]["router_fqdn"],
+        inventory=params.subscription["iptrunk"]["iptrunk_sides"][0]["iptrunk_side_node"]["router_fqdn"],
+        extra_vars=extra_vars,
+        callback=params.callback,
+    )
+
+
+@router.post("/migrate")
+def migrate_ip_trunk(params: IPTrunkMigrationParams) -> PlaybookLaunchResponse:
+    """Launch a playbook to provision a new IP trunk service.
+
+    The response will contain either a job ID, or error information.
+
+    :param params: The parameters that define the new subscription object that is to be migrated.
+    :type params: :class:`IPTrunkMigrationParams`
+    :return: Response from the Ansible runner, including a run ID.
+    :rtype: :class:`lso.playbook.PlaybookLaunchResponse`
+    """
+    extra_vars = {
+        "wfo_trunk_json": params.subscription,
+        "new_side": params.new_side,
+        "dry_run": str(params.dry_run),
+        "verb": params.verb,
+    }
+
+    return run_playbook(
+        playbook_path=path.join(config_params.ansible_playbooks_root_dir, "iptrunks.yaml"),
+        inventory=str(
+            params.subscription["iptrunk"]["iptrunk_sides"][0]["iptrunk_side_node"]["router_fqdn"]
+            + "\n"
+            + params.subscription["iptrunk"]["iptrunk_sides"][1]["iptrunk_side_node"]["router_fqdn"]
+            + "\n"
+        ),
         extra_vars=extra_vars,
         callback=params.callback,
     )
diff --git a/test/routes/test_ip_trunk.py b/test/routes/test_ip_trunk.py
index 1ca8e32e9af2edcfbb7b1ed88dfb65e08ae253c9..3f999aa9503a09177aad3e28f135c2b25974b56e 100644
--- a/test/routes/test_ip_trunk.py
+++ b/test/routes/test_ip_trunk.py
@@ -16,80 +16,137 @@ _SUBSCRIPTION_OBJECT = {
         "iptrunk_description": "A description for this trunk",
         "iptrunk_isis_metric": 9000,
         "iptrunk_minimum_links": 1,
-        "iptrunk_sideA_ae_geant_a_sid": "GA-00000",
-        "iptrunk_sideA_ae_iface": "ae0",
-        "iptrunk_sideA_ae_members": ["ge-0/0/0"],
-        "iptrunk_sideA_ae_members_description": ["this is the first interface on side A"],
-        "iptrunk_sideA_node": {
-            "router_fqdn": "rtx.city.country.geant.net",
-            "router_ias_lt_ipv4_network": "1.0.0.0/31",
-            "router_ias_lt_ipv6_network": "dead:beef::3/126",
-            "router_lo_ipv4_address": "1.0.0.0",
-            "router_lo_ipv6_address": "dead:beef::",
-            "router_lo_iso_address": "00.0000.0000.0000.0000.0000.00",
-            "router_role": "p",
-            "router_si_ipv4_network": "0.0.1.0/31",
-            "router_site": {
-                "name": "SiteBlock",
+        "iptrunk_sides": [
+            {
+                "name": "IptrunkSideBlock",
                 "label": None,
-                "site_city": "City",
-                "site_name": "city",
-                "site_tier": "1",
-                "site_country": "Country",
-                "site_latitude": 0.0,
-                "site_longitude": 0.0,
-                "site_internal_id": 0,
-                "site_country_code": "XX",
-                "owner_subscription_id": "0",
-                "site_bgp_community_id": 0,
-                "subscription_instance_id": "0",
-                "site_ts_address": "127.0.0.2",
+                "iptrunk_side_node": {
+                    "name": "RouterBlock",
+                    "label": None,
+                    "router_fqdn": "rt1.city.country.geant.net",
+                    "router_role": "p",
+                    "router_site": {
+                        "name": "SiteBlock",
+                        "label": None,
+                        "site_city": "City",
+                        "site_name": "city",
+                        "site_tier": "1",
+                        "site_country": "Country",
+                        "site_latitude": 1,
+                        "site_longitude": 1,
+                        "site_ts_address": "0.0.0.0",
+                        "site_internal_id": 1,
+                        "site_country_code": "xxx",
+                        "owner_subscription_id": "b5146e62-da79-4791-b703-d03b0ebeebf8",
+                        "site_bgp_community_id": 1,
+                        "subscription_instance_id": "039e03e5-5c09-4236-8d28-cd569e04315e",
+                    },
+                    "router_vendor": "juniper",
+                    "router_ts_port": 22222,
+                    "router_access_via_ts": True,
+                    "owner_subscription_id": "4a0001f1-459d-46f5-9a85-b8177e1bbc1b",
+                    "router_lo_iso_address": "49.51e5.0001.0620.4009.6014.00",
+                    "router_lo_ipv4_address": "0.0.0.0",
+                    "router_lo_ipv6_address": "::",
+                    "router_si_ipv4_network": "0.0.0.0/31",
+                    "router_is_ias_connected": True,
+                    "subscription_instance_id": "2242883e-a581-4ce1-919c-9c986ded57f6",
+                    "router_ias_lt_ipv4_network": "0.0.0.0/31",
+                    "router_ias_lt_ipv6_network": "::/126",
+                },
+                "iptrunk_side_ae_iface": "ae1",
+                "owner_subscription_id": "c9ddbe14-e107-4749-82ac-e22091cdb132",
+                "iptrunk_side_ae_members": ["ge-0/0/0", "ge-0/0/1"],
+                "subscription_instance_id": "6276fac5-9d31-4c9a-9247-48f02a19f151",
+                "iptrunk_side_ae_geant_a_sid": "SID-11112",
+                "iptrunk_side_ae_members_description": ["first one", "second one"],
             },
-            "router_ts_port": 22,
-            "router_vendor": "vendor",
-            "owner_subscription_id": "0",
-            "subscription_instance_id": "0",
-        },
-        "iptrunk_sideB_ae_geant_a_sid": "GA-00002",
-        "iptrunk_sideB_ae_iface": "ae0",
-        "iptrunk_sideB_ae_members": ["ge-0/0/0"],
-        "iptrunk_sideB_ae_members_description": ["this is the first interface side B"],
-        "iptrunk_sideB_node": {
-            "router_fqdn": "rtx.town.country.geant.net",
-            "router_ias_lt_ipv4_network": "0.0.0.0/31",
-            "router_ias_lt_ipv6_network": "deaf:beef::1/126",
-            "router_lo_ipv4_address": "0.0.0.0",
-            "router_lo_ipv6_address": "dead:beef::2",
-            "router_lo_iso_address": "00.0000.0000.0000.0000.0000.00",
-            "router_role": "p",
-            "router_si_ipv4_network": "0.1.0.0/31",
-            "router_site": {
-                "name": "SiteBlock",
+            {
+                "name": "IptrunkSideBlock",
                 "label": None,
-                "site_city": "Town",
-                "site_name": "town",
-                "site_tier": "1",
-                "site_country": "Country",
-                "site_latitude": 0.0,
-                "site_longitude": 0.0,
-                "site_internal_id": 1,
-                "site_country_code": "xx",
-                "owner_subscription_id": "0",
-                "site_bgp_community_id": 2,
-                "subscription_instance_id": "0",
-                "site_ts_address": "127.0.0.2",
+                "iptrunk_side_node": {
+                    "name": "RouterBlock",
+                    "label": None,
+                    "router_fqdn": "rt2.city.country.geant.net",
+                    "router_role": "p",
+                    "router_site": {
+                        "name": "SiteBlock",
+                        "label": None,
+                        "site_city": "City",
+                        "site_name": "city",
+                        "site_tier": "1",
+                        "site_country": "Country",
+                        "site_latitude": 1,
+                        "site_longitude": 1,
+                        "site_ts_address": "0.0.0.0",
+                        "site_internal_id": 2,
+                        "site_country_code": "country",
+                        "owner_subscription_id": "93cba8dc-7424-44c0-8872-13159df93042",
+                        "site_bgp_community_id": 2,
+                        "subscription_instance_id": "6bf4f274-6496-438d-9dba-9c3984d0ec07",
+                    },
+                    "router_vendor": "juniper",
+                    "router_ts_port": 11111,
+                    "router_access_via_ts": True,
+                    "owner_subscription_id": "9cb1fc7d-9608-42ce-aacc-2a97f9620a91",
+                    "router_lo_iso_address": "49.51e5.0001.0620.4009.6066.00",
+                    "router_lo_ipv4_address": "0.0.0.0",
+                    "router_lo_ipv6_address": "::",
+                    "router_si_ipv4_network": "0.0.0.0/31",
+                    "router_is_ias_connected": True,
+                    "subscription_instance_id": "6d09394e-658b-4e55-8b1f-8b812d59f5a1",
+                    "router_ias_lt_ipv4_network": "0.0.0.0/31",
+                    "router_ias_lt_ipv6_network": "::/126",
+                },
+                "iptrunk_side_ae_iface": "ae1",
+                "owner_subscription_id": "c9ddbe14-e107-4749-82ac-e22091cdb132",
+                "iptrunk_side_ae_members": ["ge-0/0/0", "ge-0/0/1"],
             },
-            "router_ts_port": 22,
-            "router_vendor": "vendor",
-            "owner_subscription_id": "0",
-            "subscription_instance_id": "0",
-        },
-        "iptrunk_speed": "1",
-        "iptrunk_type": "Dark_fiber",
+        ],
     },
     "status": "provisioning",
 }
 
+_MIGRATION_OBJECT = {
+    "new_node": {
+        "name": "RouterBlock",
+        "label": None,
+        "router_fqdn": "rt2.city.country.geant.net",
+        "router_role": "p",
+        "router_site": {
+            "name": "SiteBlock",
+            "label": None,
+            "site_city": "City",
+            "site_name": "city",
+            "site_tier": "1",
+            "site_country": "Country",
+            "site_latitude": 1,
+            "site_longitude": 1,
+            "site_ts_address": "0.0.0.0",
+            "site_internal_id": 2,
+            "site_country_code": "country",
+            "owner_subscription_id": "93cba8dc-7424-44c0-8872-13159df93042",
+            "site_bgp_community_id": 2,
+            "subscription_instance_id": "6bf4f274-6496-438d-9dba-9c3984d0ec07",
+        },
+        "router_vendor": "juniper",
+        "router_ts_port": 11111,
+        "router_access_via_ts": True,
+        "owner_subscription_id": "9cb1fc7d-9608-42ce-aacc-2a97f9620a91",
+        "router_lo_iso_address": "49.51e5.0001.0620.4009.6066.00",
+        "router_lo_ipv4_address": "0.0.0.0",
+        "router_lo_ipv6_address": "::",
+        "router_si_ipv4_network": "0.0.0.0/31",
+        "router_is_ias_connected": True,
+        "subscription_instance_id": "6d09394e-658b-4e55-8b1f-8b812d59f5a1",
+        "router_ias_lt_ipv4_network": "0.0.0.0/31",
+        "router_ias_lt_ipv6_network": "::/126",
+    },
+    "new_lag_interface": "ae1",
+    "new_lag_member_interfaces": ["ge-0/0/0", "ge-0/0/1"],
+    "replace_index": 0,
+}
+
 
 @responses.activate
 def test_ip_trunk_provisioning(client: TestClient) -> None:
@@ -158,3 +215,28 @@ def test_ip_trunk_deletion(client: TestClient) -> None:
     responses.assert_call_count(TEST_CALLBACK_URL, 1)
 
     assert response["status"] == "ok"
+
+
+@responses.activate
+def test_ip_trunk_migration(client: TestClient) -> None:
+    responses.put(url=TEST_CALLBACK_URL, status=204)
+
+    params = {
+        "callback": TEST_CALLBACK_URL,
+        "dry_run": True,
+        "verb": "migrate",
+        "subscription": _SUBSCRIPTION_OBJECT,
+        "new_side": _MIGRATION_OBJECT,
+    }
+
+    with patch("lso.playbook.ansible_runner.run", new=test_ansible_runner_run) as _:
+        rv = client.request(url="/api/ip_trunk/migrate", method=responses.POST, json=params)
+        assert rv.status_code == 200
+        response = rv.json()
+        #  Wait a second for the run to finish
+        time.sleep(1)
+
+    jsonschema.validate(response, PlaybookLaunchResponse.model_json_schema())
+    responses.assert_call_count(TEST_CALLBACK_URL, 1)
+
+    assert response["status"] == "ok"