diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b9182d578851b287fc62bb7e2e87ddd4ed59c1dc..6a311813a13e330d7a9e5241f35a143211d047d7 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -61,7 +61,7 @@ build-documentation:
 lint-documentation:
   stage: documentation
   image:
-    name: jdkato/vale:latest
+    name: jdkato/vale:v3.9.1
     entrypoint: [""]
   tags:
     - docker-executor
diff --git a/Changelog.md b/Changelog.md
index 13e571501e419ca1f6cd61ddd14cec2cd0e40ab8..6f7bae345e93cc40378a15e4f7bfc0e0502bbff7 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,5 +1,13 @@
 # Changelog
 
+## [2.28] - 2024-12-13
+- Fix an issue where LSO could receive special characters from a workflow state.
+- Unify GÉANT service ID resource type naming, and add sequence to the database for generating them.
+- Fix a bug in the Edge Port validation workflow
+- Add support for DCN VLAN numbering in the LAN Switch Interconnect product.
+- Auto-generate IP resources when creating a new LAN Switch Interconnect based on the Site it's in.
+- Update documentation.
+
 ## [2.27] - 2024-12-03
 - Add support for VRFs
 - Rework the way code documentation is built and pulished
diff --git a/Dockerfile b/Dockerfile
index b2802dd07a91be30f25831fc71f97361e2ac46d1..646bd9f13042d70535a85b4f669e3cdf36e6b674 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM python:3.12-alpine
+FROM python:3.12.7-alpine
 WORKDIR /app
 
 ARG ARTIFACT_VERSION
diff --git a/docs/build-docs.sh b/docs/build-docs.sh
index dc4c92c014ab21ce6405b7c765bf6fd7aa35b178..6da63d82bed8839ab2e2d068bd8a86b245fe06d6 100755
--- a/docs/build-docs.sh
+++ b/docs/build-docs.sh
@@ -5,7 +5,7 @@ set -o nounset
 export OSS_PARAMS_FILENAME=../gso/oss-params-example.json
 export TESTING=true
 
-pip install pyyaml mkdocstrings-python mkdocs_gen_files mkdocs-material mkdocs-literate-nav mkdocs-redirects
+pip install pyyaml mkdocstrings-python mkdocs_gen_files mkdocs-material mkdocs-literate-nav mkdocs-redirects mkdocs-open-in-new-tab
 pip install -e ..
 python ./scripts/gen_wf_redirects.py
 
diff --git a/docs/includes/glossary.md b/docs/includes/glossary.md
index 4df9f9940521f78574c848c2f49bcfa76dd7899f..a29d245259baa8a8b27cd16feade66bd5fa6c732 100644
--- a/docs/includes/glossary.md
+++ b/docs/includes/glossary.md
@@ -5,6 +5,7 @@
 *[CFS]: Customer Facing Service
 *[CIDR]: Classless Inter-Domain Routing
 *[DCIM]: Datacenter Infrastructure Manager
+*[DCN]: Datacenter Network
 *[DHCP]: Dynamic Host Configuration Protocol
 *[DNS]: Domain Name System
 *[DTAP]: Development, Testing, Acceptance, and Production
diff --git a/docs/scripts/gen_wf_redirects.py b/docs/scripts/gen_wf_redirects.py
index c4c0d8b321566628a83dd7eea861e8b1486301c4..90b84444d2dbebfc339b24a07f4dfb5a011da74c 100644
--- a/docs/scripts/gen_wf_redirects.py
+++ b/docs/scripts/gen_wf_redirects.py
@@ -29,10 +29,11 @@ with Path.open(root / "docs" / "wf_redirects.yaml", "w") as redirect_file:
     file_content = {
         "plugins": [
             "search",
+            {"open-in-new-tab": {"add_icon": True}},
             {"gen-files": {"scripts": ["scripts/gen_ref_pages.py"]}},
             {"redirects": {"redirect_maps": redirect_map}},
             {"literate-nav": {"nav_file": "SUMMARY.md"}},
-            "mkdocstrings",
+            {"mkdocstrings": {"handlers": {"python": {"options": {"filters": [], "members_order": "source"}}}}},
         ]
     }
     yaml.dump(file_content, redirect_file)
diff --git a/docs/source/admin_guide/wfo/overview.md b/docs/source/admin_guide/wfo/overview.md
index d8af9f500a4fc8f6d57d8aba173fc2c67ed1b01b..6ec77a2a7ecdd83cc3b3cf038cd8835f6fe0920f 100644
--- a/docs/source/admin_guide/wfo/overview.md
+++ b/docs/source/admin_guide/wfo/overview.md
@@ -22,7 +22,7 @@ classDiagram
 
 A node consists of one or more routers, a switch, and a terminal server. 
 In general -- as laid out more extensively 
-<a href="https://wiki.geant.org/display/NETENG/001+-+Topology+and+physical+layout" target="_blank">here</a> 
+[here](https://wiki.geant.org/display/NETENG/001+-+Topology%2C+physical+layout+and+site+design) 
 (behind login) -- a PoP consists of:
 
 * One or two routers
diff --git a/docs/source/architecture/components/index.md b/docs/source/architecture/components/index.md
index bf0790f34cf09df7efab943a2437df16579319d0..76e837d67ef23a5442bf1a7b869d56cddc801a1b 100644
--- a/docs/source/architecture/components/index.md
+++ b/docs/source/architecture/components/index.md
@@ -1,7 +1,7 @@
 # Components of GAP
 
 As stated before, GAP is a platform and not a monolithic piece of software. GAP interacts with different OSS/BSS
-systems already present in GÉANT and these are tightly integrated with the automation platform.
+systems already present in GÉANT and these are integrated with the automation platform.
 
 From a high level point of view, GAP can be seen as the sum of the following parts:
 
diff --git a/docs/source/architecture/components/lso/index.md b/docs/source/architecture/components/lso/index.md
index a1e1e0d44c7c791b08441e7931bb61ea9137532a..bcfc35f9f6057d3adae25bbe9f8ed958bbf315a6 100644
--- a/docs/source/architecture/components/lso/index.md
+++ b/docs/source/architecture/components/lso/index.md
@@ -18,11 +18,9 @@ library dependencies.
 
 ## Inner workings
 
-LSO uses <a href="https://ansible.readthedocs.io/projects/runner/en/latest/"
-target="_blank">`ansible-runner`</a> for the execution of Ansible playbooks.
-This package fully dictates the way in which GAP interacts with Ansible itself.
-LSO only introduces an API with a single REST endpoint that exposes its
-functionality.
+LSO uses [`ansible-runner`](https://ansible.readthedocs.io/projects/runner/en/latest/) for the execution of Ansible
+playbooks.  This package fully dictates the way in which GAP interacts with Ansible itself. LSO only introduces an API
+with a single REST endpoint that exposes its functionality.
 
 In the case of GAP, all Ansible playbooks operate without an inventory that
 contains all relevant `group_vars` and `host_vars`. The inventory is passed to
@@ -148,15 +146,12 @@ full-fledged API request to LSO, an example call is given.
 
 ## Code documentation
 
-Code documentation for LSO can be found
-<a href="https://workfloworchestrator.org/lso" target="_blank">here</a>.
+Code documentation for LSO can be found [here](https://workfloworchestrator.org/lso).
 
 ## Deployment within GÉANT
 
-For the deployment in GÉANT, LSO runs inside a Docker container. The Dockerfile
-used to build this container is available <a href=
-"https://gitlab.software.geant.org/goat/gap/lso/-/blob/develop/Dockerfile"
-target="_blank">here</a>.
+For the deployment in GÉANT, LSO runs inside a Docker container. A Dockerfile that can be used to build this container
+is available [here](https://github.com/workfloworchestrator/lso/blob/main/Dockerfile.example).
 
 When building the Docker image, some Ansible roles and collections are installed
 that are required for interacting with Juniper and Nokia equipment. For another
diff --git a/docs/source/architecture/index.md b/docs/source/architecture/index.md
index 7f53acba3a6d6229d0dbe743fc907b0c1d4f00a9..da9f309c04eb4c4c409b4d4a28c894c5de95bf81 100644
--- a/docs/source/architecture/index.md
+++ b/docs/source/architecture/index.md
@@ -57,16 +57,12 @@ configuration backups of routers, switches, and any other network devices that a
 More detailed information about this integration is available in the
 [LibreNMS integration module](../admin_guide/oss_bss/librenms.md).
 
-### Kentik (planned)
+### Kentik
 
 Kentik is a Network Observability tool which collects various data points from deployed PE routers.
 For this reason it is not in scope for PHASE1.
 
-### Inventory provider (planned)
+### Inventory provider
 
 At the time of writing, the Inventory Provider gets the list of routers from the network engineering SOT servers.
 This will change and Inventory Provider is then able to directly query CoreDB.
-
-## Interaction with a technical domain: IP/MPLS
-
-TBA
diff --git a/docs/vale/.vale.ini b/docs/vale/.vale.ini
index 06742e1a587711316b5140adf0c4bb7a86099bab..a5a4c9cc53200bdab311a353d7fc2ac9233674c9 100644
--- a/docs/vale/.vale.ini
+++ b/docs/vale/.vale.ini
@@ -33,10 +33,10 @@ Microsoft.SentenceLength = NO
 
 ; This statement ignores the names of parameters in docstrings, the four spaces that prepend it and the one following it
 ; are necessary.
-TokenIgnores =     \S+:
+TokenIgnores = (?: *\S+: ), (?:`\S+`)
 
 [*.md]
 BasedOnStyles = Vale, proselint, Microsoft
 
 [formats]
-py = rst
+py = md
diff --git a/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt b/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt
index 6373503d95c7d4af174b0bb9a34cb988ec00a402..aeb0c40d8ae8adc250f8ab38c568ad95d4855393 100644
--- a/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt
+++ b/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt
@@ -11,6 +11,7 @@ CIDR
 CFS
 CNAME
 DCIM
+DCN
 DDI
 DHCP
 DNS
@@ -90,3 +91,4 @@ WFO
 dry_run
 eBGP
 iBGP
+disable?[sd]
diff --git a/gso/api/v1/network.py b/gso/api/v1/network.py
index e4a19de9e90475f70261e4bf408498de68a95af9..81a209b7e4365f8a4d3e90573a1160ea02ea1131 100644
--- a/gso/api/v1/network.py
+++ b/gso/api/v1/network.py
@@ -64,7 +64,7 @@ class IptrunkBlock(OrchestratorBaseModel):
     iptrunk_capacity: str
     iptrunk_isis_metric: int
     iptrunk_sides: list[IptrunkSideBlock]
-    geant_s_sid: str
+    gs_id: str
 
 
 class IptrunkSchema(OrchestratorBaseModel):
@@ -109,7 +109,7 @@ def network_topology() -> NetworkTopologyDomainModelSchema:
                 "iptrunk_capacity": _calculate_iptrunk_capacity(
                     extended_model["iptrunk"]["iptrunk_sides"], extended_model["iptrunk"]["iptrunk_speed"]
                 ),
-                "geant_s_sid": extended_model["iptrunk"]["geant_s_sid"],
+                "gs_id": extended_model["iptrunk"]["gs_id"],
                 "iptrunk_sides": [
                     {
                         "subscription_instance_id": side["subscription_instance_id"],
diff --git a/gso/auth/oidc.py b/gso/auth/oidc.py
index 02720fe170e55250218f5bdc2f722add2b88d3ad..ccbbe16382de30d092fd3d90c534d7499757cfc7 100644
--- a/gso/auth/oidc.py
+++ b/gso/auth/oidc.py
@@ -43,7 +43,7 @@ def ensure_openid_config_loaded(func: Callable) -> Callable:
 
 
 class OIDCAuthentication(OIDCAuth):
-    """OIDCUser class extends the ``HTTPBearer`` class to do extra verification.
+    """OIDCUser class extends the `HTTPBearer` class to do extra verification.
 
     The class will act as follows: Validate the Credentials at the AAI proxy by calling the UserInfo endpoint.
     """
diff --git a/gso/cli/imports.py b/gso/cli/imports.py
index 14a45ad05810386d27abc66cc68129f668e0930c..fc81b03b3f3c45c54f95db64a872c91cbeee1be6 100644
--- a/gso/cli/imports.py
+++ b/gso/cli/imports.py
@@ -40,20 +40,20 @@ from gso.services.subscriptions import (
 )
 from gso.utils.shared_enums import SBPType, Vendor
 from gso.utils.types.base_site import BaseSiteValidatorModel
+from gso.utils.types.geant_ids import IMPORTED_GA_ID, IMPORTED_GS_ID
 from gso.utils.types.interfaces import BandwidthString, LAGMember, LAGMemberList, PhysicalPortCapacity
 from gso.utils.types.ip_address import (
-    AddressSpace,
     IPAddress,
     IPv4AddressType,
-    IPV4Netmask,
-    IPv4NetworkType,
+    IPv4Netmask,
     IPv6AddressType,
-    IPV6Netmask,
+    IPv6Netmask,
     PortNumber,
 )
 from gso.utils.types.virtual_identifiers import VC_ID, VLAN_ID
 
 app: typer.Typer = typer.Typer()
+IMPORT_WAIT_MESSAGE = "Waiting for the dust to settle before importing new products..."
 
 
 class CreatePartner(BaseModel):
@@ -64,11 +64,11 @@ class CreatePartner(BaseModel):
 
 
 class SiteImportModel(BaseSiteValidatorModel):
-    """The required input for importing an existing ``gso.products.product_types.site``."""
+    """The required input for importing an existing `gso.products.product_types.site`."""
 
 
 class RouterImportModel(BaseModel):
-    """Required fields for importing an existing ``gso.product.product_types.router``."""
+    """Required fields for importing an existing `gso.product.product_types.router`."""
 
     partner: str
     router_site: str
@@ -82,7 +82,7 @@ class RouterImportModel(BaseModel):
 
 
 class SwitchImportModel(BaseModel):
-    """Required fields for importing an existing ``gso.product.product_types.switch``."""
+    """Required fields for importing an existing `gso.product.product_types.switch`."""
 
     fqdn: str
     ts_port: PortNumber
@@ -92,7 +92,7 @@ class SwitchImportModel(BaseModel):
 
 
 class SuperPopSwitchImportModel(BaseModel):
-    """Required fields for importing an existing ``gso.product.product_types.super_pop_switch``."""
+    """Required fields for importing an existing `gso.product.product_types.super_pop_switch`."""
 
     partner: str
     super_pop_switch_site: str
@@ -102,7 +102,7 @@ class SuperPopSwitchImportModel(BaseModel):
 
 
 class OfficeRouterImportModel(BaseModel):
-    """Required fields for importing an existing ``gso.product.product_types.office_router``."""
+    """Required fields for importing an existing `gso.product.product_types.office_router`."""
 
     partner: str
     office_router_site: str
@@ -113,10 +113,10 @@ class OfficeRouterImportModel(BaseModel):
 
 
 class IptrunkImportModel(BaseModel):
-    """Required fields for importing an existing ``gso.products.product_types.iptrunk``."""
+    """Required fields for importing an existing `gso.products.product_types.iptrunk`."""
 
     partner: str
-    geant_s_sid: str | None
+    gs_id: IMPORTED_GS_ID | None
     iptrunk_type: IptrunkType
     iptrunk_description: str | None = None
     iptrunk_speed: PhysicalPortCapacity
@@ -124,11 +124,11 @@ class IptrunkImportModel(BaseModel):
     iptrunk_isis_metric: int
     side_a_node_id: str
     side_a_ae_iface: str
-    side_a_ae_geant_a_sid: str | None
+    side_a_ga_id: IMPORTED_GA_ID | None
     side_a_ae_members: LAGMemberList[LAGMember]
     side_b_node_id: str
     side_b_ae_iface: str
-    side_b_ae_geant_a_sid: str | None
+    side_b_ga_id: IMPORTED_GA_ID | None
     side_b_ae_members: LAGMemberList[LAGMember]
 
     iptrunk_ipv4_network: ipaddress.IPv4Network
@@ -178,7 +178,7 @@ class IptrunkImportModel(BaseModel):
 
 
 class OpenGearImportModel(BaseModel):
-    """Required fields for importing an existing ``gso.products.product_types.opengear``."""
+    """Required fields for importing an existing `gso.products.product_types.opengear`."""
 
     partner: str
     opengear_site: str
@@ -189,7 +189,7 @@ class OpenGearImportModel(BaseModel):
 
 
 class EdgePortImportModel(BaseModel):
-    """Required fields for importing an existing ``gso.products.product_types.edge_port``."""
+    """Required fields for importing an existing `gso.products.product_types.edge_port`."""
 
     node: str
     service_type: EdgePortType
@@ -197,7 +197,7 @@ class EdgePortImportModel(BaseModel):
     encapsulation: EncapsulationType
     name: str
     minimum_links: int
-    geant_ga_id: str | None
+    ga_id: IMPORTED_GA_ID | None
     mac_address: str | None
     partner: str
     enable_lacp: bool
@@ -269,15 +269,15 @@ class L3CoreServiceImportModel(BaseModel):
 
         edge_port: str
         ap_type: str
-        geant_sid: str
+        gs_id: IMPORTED_GS_ID
         sbp_type: SBPType = SBPType.L3
         is_tagged: bool = False
         vlan_id: VLAN_ID
         custom_firewall_filters: bool = False
         ipv4_address: IPv4AddressType
-        ipv4_mask: IPV4Netmask
+        ipv4_mask: IPv4Netmask
         ipv6_address: IPv6AddressType
-        ipv6_mask: IPV6Netmask
+        ipv6_mask: IPv6Netmask
         is_multi_hop: bool = True
         bgp_peers: list["L3CoreServiceImportModel.BaseBGPPeer"]
         v4_bfd_settings: "L3CoreServiceImportModel.BFDSettingsModel"
@@ -315,7 +315,6 @@ class LanSwitchInterconnectRouterSideImportModel(BaseModel):
     node: UUIDstr
     ae_iface: str
     ae_members: LAGMemberList[LAGMember]
-    ipv4_address: IPv4AddressType
 
 
 class LanSwitchInterconnectSwitchSideImportModel(BaseModel):
@@ -324,15 +323,12 @@ class LanSwitchInterconnectSwitchSideImportModel(BaseModel):
     switch: UUIDstr
     ae_iface: str
     ae_members: LAGMemberList[LAGMember]
-    ipv4_address: IPv4AddressType
 
 
 class LanSwitchInterconnectImportModel(BaseModel):
     """Import LAN Switch Interconnect model."""
 
     lan_switch_interconnect_description: str
-    lan_switch_interconnect_ip_network: IPv4NetworkType | None
-    address_space: AddressSpace
     minimum_links: int
     router_side: LanSwitchInterconnectRouterSideImportModel
     switch_side: LanSwitchInterconnectSwitchSideImportModel
@@ -349,7 +345,7 @@ class Layer2CircuitServiceImportModel(BaseModel):
 
     service_type: Layer2CircuitServiceType
     partner: str
-    geant_sid: str
+    gs_id: IMPORTED_GS_ID
     vc_id: VC_ID
     layer_2_circuit_side_a: ServiceBindingPortInput
     layer_2_circuit_side_b: ServiceBindingPortInput
@@ -456,7 +452,7 @@ def _generic_import_product(
         except ValidationError as e:
             typer.echo(f"Validation error: {e}")
 
-    typer.echo("Waiting for the dust to settle before moving on the importing new products...")
+    typer.echo(IMPORT_WAIT_MESSAGE)
     time.sleep(1)
 
     #  Migrate new products from imported to "full" counterpart.
@@ -544,7 +540,7 @@ def import_edge_port(filepath: str = common_filepath_option) -> None:
         except ValidationError as e:
             typer.echo(f"Validation error: {e}")
 
-    typer.echo("Waiting for the dust to settle before moving on the importing new products...")
+    typer.echo(IMPORT_WAIT_MESSAGE)
     time.sleep(1)
 
     edge_port_ids = get_subscriptions(
@@ -592,7 +588,7 @@ def import_iptrunks(filepath: str = common_filepath_option) -> None:
         try:
             initial_data = IptrunkImportModel(
                 partner="GEANT",
-                geant_s_sid=trunk["id"],
+                gs_id=trunk["id"],
                 iptrunk_type=trunk["config"]["common"]["type"],
                 iptrunk_description=trunk["config"]["common"].get("description", ""),
                 iptrunk_speed=trunk["config"]["common"]["link_speed"],
@@ -600,11 +596,11 @@ def import_iptrunks(filepath: str = common_filepath_option) -> None:
                 iptrunk_isis_metric=trunk["config"]["common"]["isis_metric"],
                 side_a_node_id=_get_router_subscription_id(trunk["config"]["nodeA"]["name"]) or "",
                 side_a_ae_iface=trunk["config"]["nodeA"]["ae_name"],
-                side_a_ae_geant_a_sid=trunk["config"]["nodeA"]["port_sid"],
+                side_a_ga_id=trunk["config"]["nodeA"]["port_ga_id"],
                 side_a_ae_members=trunk["config"]["nodeA"]["members"],
                 side_b_node_id=_get_router_subscription_id(trunk["config"]["nodeB"]["name"]) or "",
                 side_b_ae_iface=trunk["config"]["nodeB"]["ae_name"],
-                side_b_ae_geant_a_sid=trunk["config"]["nodeB"]["port_sid"],
+                side_b_ga_id=trunk["config"]["nodeB"]["port_ga_id"],
                 side_b_ae_members=trunk["config"]["nodeB"]["members"],
                 iptrunk_ipv4_network=iptrunk_ipv4_network,
                 iptrunk_ipv6_network=iptrunk_ipv6_network,
@@ -615,7 +611,7 @@ def import_iptrunks(filepath: str = common_filepath_option) -> None:
         except ValidationError as e:
             typer.echo(f"Validation error: {e}")
 
-    typer.echo("Waiting for the dust to settle before moving on the importing new products...")
+    typer.echo(IMPORT_WAIT_MESSAGE)
     time.sleep(1)
 
     trunk_ids = get_subscriptions(
@@ -675,7 +671,7 @@ def import_l3_core_service(filepath: str = common_filepath_option) -> None:
         except ValidationError as e:
             typer.echo(f"Validation error: {e}")
 
-    typer.echo("Waiting for the dust to settle before importing new products...")
+    typer.echo(IMPORT_WAIT_MESSAGE)
     time.sleep(1)
 
     # Migrate new products from imported to "full" counterpart.
@@ -733,7 +729,7 @@ def import_layer_2_circuit_service(filepath: str = common_filepath_option) -> No
             )
         except ValidationError as e:
             typer.echo(f"Validation error: {e}")
-    typer.echo("Waiting for the dust to settle before importing new products...")
+    typer.echo(IMPORT_WAIT_MESSAGE)
     time.sleep(1)
 
     # Migrate new products from imported to "full" counterpart.
diff --git a/gso/migrations/versions/2024-11-04_e854e0c35e20_update_lan_switch_interconnect.py b/gso/migrations/versions/2024-11-04_e854e0c35e20_update_lan_switch_interconnect.py
new file mode 100644
index 0000000000000000000000000000000000000000..71b2874c06ed054fb2d65f6c933aa8275f2048ef
--- /dev/null
+++ b/gso/migrations/versions/2024-11-04_e854e0c35e20_update_lan_switch_interconnect.py
@@ -0,0 +1,48 @@
+"""Update LAN Switch Interconnect.
+
+Revision ID: e854e0c35e20
+Revises: 79192e72131c
+Create Date: 2024-11-04 17:21:14.612740
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = 'e854e0c35e20'
+down_revision = '79192e72131c'
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    conn.execute(sa.text("""
+DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectSwitchSideBlock', 'LanSwitchInterconnectRouterSideBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ipv4_address'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectSwitchSideBlock', 'LanSwitchInterconnectRouterSideBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ipv4_address'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('address_space'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('address_space'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('lan_switch_interconnect_ip_network'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('lan_switch_interconnect_ip_network'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instance_values WHERE subscription_instance_values.resource_type_id IN (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('address_space', 'lan_switch_interconnect_ip_network'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM resource_types WHERE resource_types.resource_type IN ('address_space', 'lan_switch_interconnect_ip_network')
+    """))
+
+
+def downgrade() -> None:
+    conn = op.get_bind()
+
diff --git a/gso/migrations/versions/2024-12-03_79a76b22ca53_clean_up_deprecated.py b/gso/migrations/versions/2024-12-03_79a76b22ca53_clean_up_deprecated.py
new file mode 100644
index 0000000000000000000000000000000000000000..348f0467dcbadf15659b330989a6e495695de768
--- /dev/null
+++ b/gso/migrations/versions/2024-12-03_79a76b22ca53_clean_up_deprecated.py
@@ -0,0 +1,33 @@
+"""Clean up deprecated references to NREN-names products
+
+Revision ID: 79a76b22ca53
+Revises: e854e0c35e20
+Create Date: 2024-12-03 17:01:33.752966
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = '79a76b22ca53'
+down_revision = 'e854e0c35e20'
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    conn.execute(sa.text("""
+DELETE FROM product_product_blocks WHERE product_product_blocks.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('GÉANT IP', 'IAS', 'Imported GÉANT IP', 'Imported IAS')) AND product_product_blocks.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('NRENL3CoreServiceBlock'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instances WHERE subscription_instances.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('NRENL3CoreServiceBlock', 'NRENAccessPort'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM product_blocks WHERE product_blocks.name IN ('NRENL3CoreServiceBlock', 'NRENAccessPort')
+    """))
+
+
+def downgrade() -> None:
+    conn = op.get_bind()
+
diff --git a/gso/migrations/versions/2024-12-04_e358efe9ab03_rename_iptrunk_and_iptrunk_side_sid_and_.py b/gso/migrations/versions/2024-12-04_e358efe9ab03_rename_iptrunk_and_iptrunk_side_sid_and_.py
new file mode 100644
index 0000000000000000000000000000000000000000..db1e58b724e0ec753365e89c9c36959750269106
--- /dev/null
+++ b/gso/migrations/versions/2024-12-04_e358efe9ab03_rename_iptrunk_and_iptrunk_side_sid_and_.py
@@ -0,0 +1,35 @@
+"""Rename iptrunk and iptrunk side sid and gid.
+
+Revision ID: e358efe9ab03
+Revises: 28c1723c6a00
+Create Date: 2024-12-04 15:05:46.356709
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = 'e358efe9ab03'
+down_revision = '28c1723c6a00'
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    conn.execute(sa.text("""
+UPDATE resource_types SET resource_type='ga_id' WHERE resource_types.resource_type = 'iptrunk_side_ae_geant_a_sid'
+    """))
+    conn.execute(sa.text("""
+UPDATE resource_types SET resource_type='gs_id' WHERE resource_types.resource_type = 'geant_s_sid'
+    """))
+
+
+def downgrade() -> None:
+    conn = op.get_bind()
+    conn.execute(sa.text("""
+UPDATE resource_types SET resource_type='iptrunk_side_ae_geant_a_sid' WHERE resource_types.resource_type = 'ga_id'
+    """))
+    conn.execute(sa.text("""
+UPDATE resource_types SET resource_type='geant_s_sid' WHERE resource_types.resource_type = 'gs_id'
+    """))
diff --git a/gso/migrations/versions/2024-12-04_e36b3bd8a45c_rename_edgeport_and_sbp_gs_and_ga_id.py b/gso/migrations/versions/2024-12-04_e36b3bd8a45c_rename_edgeport_and_sbp_gs_and_ga_id.py
new file mode 100644
index 0000000000000000000000000000000000000000..180c97ee45b08904fc7c4b4dbd1ea2b6a36d1865
--- /dev/null
+++ b/gso/migrations/versions/2024-12-04_e36b3bd8a45c_rename_edgeport_and_sbp_gs_and_ga_id.py
@@ -0,0 +1,47 @@
+"""Rename edgeport and sbp gs and ga id.
+
+Revision ID: e36b3bd8a45c
+Revises: e358efe9ab03
+Create Date: 2024-12-04 15:08:30.512126
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = 'e36b3bd8a45c'
+down_revision = 'e358efe9ab03'
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    conn.execute(sa.text("""
+UPDATE product_block_resource_types SET resource_type_id=(SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ga_id')) WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('EdgePortBlock')) AND product_block_resource_types.resource_type_id IN (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('geant_ga_id'))
+    """))
+    conn.execute(sa.text("""
+UPDATE product_block_resource_types SET resource_type_id=(SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('gs_id')) WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('ServiceBindingPort')) AND product_block_resource_types.resource_type_id IN (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('geant_sid'))
+    """))
+    conn.execute(sa.text("""
+UPDATE subscription_instance_values SET resource_type_id=(SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ga_id')) WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('EdgePortBlock'))) AND subscription_instance_values.resource_type_id IN (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('geant_ga_id'))
+    """))
+    conn.execute(sa.text("""
+UPDATE subscription_instance_values SET resource_type_id=(SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('gs_id')) WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('ServiceBindingPort'))) AND subscription_instance_values.resource_type_id IN (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('geant_sid'))
+    """))
+
+
+def downgrade() -> None:
+    conn = op.get_bind()
+    conn.execute(sa.text("""
+UPDATE product_block_resource_types SET resource_type_id=(SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('geant_ga_id')) WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('EdgePortBlock')) AND product_block_resource_types.resource_type_id IN (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ga_id'))
+    """))
+    conn.execute(sa.text("""
+UPDATE product_block_resource_types SET resource_type_id=(SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('geant_sid')) WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('ServiceBindingPort')) AND product_block_resource_types.resource_type_id IN (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('gs_id'))
+    """))
+    conn.execute(sa.text("""
+UPDATE subscription_instance_values SET resource_type_id=(SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('geant_ga_id')) WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('EdgePortBlock'))) AND subscription_instance_values.resource_type_id IN (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ga_id'))
+    """))
+    conn.execute(sa.text("""
+UPDATE subscription_instance_values SET resource_type_id=(SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('geant_sid')) WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('ServiceBindingPort'))) AND subscription_instance_values.resource_type_id IN (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('gs_id'))
+    """))
diff --git a/gso/migrations/versions/2024-12-04_fc7bd696014e_add_optical_equipment_attribute_to_site.py b/gso/migrations/versions/2024-12-04_fc7bd696014e_add_optical_equipment_attribute_to_site.py
new file mode 100644
index 0000000000000000000000000000000000000000..80f277e04f0ff5513e394c9ce02a550bc7ba52e4
--- /dev/null
+++ b/gso/migrations/versions/2024-12-04_fc7bd696014e_add_optical_equipment_attribute_to_site.py
@@ -0,0 +1,44 @@
+"""Add optical equipment attribute to Site.
+
+Revision ID: fc7bd696014e
+Revises: 79a76b22ca53
+Create Date: 2024-12-04 10:15:40.529552
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = 'fc7bd696014e'
+down_revision = '79a76b22ca53'
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('site_contains_optical_equipment', 'Whether a site contains optical equipment') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_contains_optical_equipment')))
+    """))
+    conn.execute(sa.text("""
+WITH rt_id AS (SELECT resource_type_id FROM resource_types WHERE resource_type = 'site_contains_optical_equipment') INSERT INTO subscription_instance_values (subscription_instance_id, resource_type_id, value) SELECT subscription_instance_id, rt_id.resource_type_id, 'True' FROM rt_id, subscription_instances WHERE product_block_id = (SELECT product_block_id FROM product_blocks WHERE name = 'SiteBlock');
+    """))
+
+
+def downgrade() -> None:
+    conn = op.get_bind()
+    conn.execute(sa.text("""
+DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_contains_optical_equipment'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_contains_optical_equipment'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instance_values WHERE subscription_instance_values.resource_type_id IN (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_contains_optical_equipment'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM resource_types WHERE resource_types.resource_type IN ('site_contains_optical_equipment')
+    """))
diff --git a/gso/migrations/versions/2024-12-05_79192e72131c_add_gs_and_ga_sequences.py b/gso/migrations/versions/2024-12-05_79192e72131c_add_gs_and_ga_sequences.py
new file mode 100644
index 0000000000000000000000000000000000000000..2fb7fc2dd3f7f4a8e10b9f7b5ade94b90dca9da7
--- /dev/null
+++ b/gso/migrations/versions/2024-12-05_79192e72131c_add_gs_and_ga_sequences.py
@@ -0,0 +1,42 @@
+"""Add GS and GA sequences.
+
+Revision ID: 79192e72131c
+Revises: e36b3bd8a45c
+Create Date: 2024-12-05 11:11:41.048264
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = '79192e72131c'
+down_revision = 'e36b3bd8a45c'
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+    # Create GS ID sequence
+    op.execute("""
+    CREATE SEQUENCE gs_id_seq
+        START WITH 50000
+        INCREMENT BY 1
+        MINVALUE 50000
+        MAXVALUE 99999
+        NO CYCLE;
+    """)
+
+    # Create GA ID sequence
+    op.execute("""
+    CREATE SEQUENCE ga_id_seq
+        START WITH 50000
+        INCREMENT BY 1
+        MINVALUE 50000
+        MAXVALUE 99999
+        NO CYCLE;
+    """)
+
+
+def downgrade() -> None:
+    op.execute("DROP SEQUENCE gs_id_seq")
+    op.execute("DROP SEQUENCE ga_id_seq")
diff --git a/gso/migrations/versions/2024-12-09_818d4ffe65df_add_vlan_ids_to_lan_switch_interconnect.py b/gso/migrations/versions/2024-12-09_818d4ffe65df_add_vlan_ids_to_lan_switch_interconnect.py
new file mode 100644
index 0000000000000000000000000000000000000000..004f37c3a90a9f72ca119ff906a9b439650ee20e
--- /dev/null
+++ b/gso/migrations/versions/2024-12-09_818d4ffe65df_add_vlan_ids_to_lan_switch_interconnect.py
@@ -0,0 +1,53 @@
+"""Add VLAN IDs to LAN Switch Interconnect.
+
+Revision ID: 818d4ffe65df
+Revises: fc7bd696014e
+Create Date: 2024-12-09 11:11:35.239599
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = '818d4ffe65df'
+down_revision = 'fc7bd696014e'
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('switch_management_vlan_id', 'VLAN ID of the switch management network') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('dcn_management_vlan_id', 'VLAN ID of the DCN management network') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('switch_management_vlan_id')))
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('dcn_management_vlan_id')))
+    """))
+
+
+def downgrade() -> None:
+    conn = op.get_bind()
+    conn.execute(sa.text("""
+DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('switch_management_vlan_id'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('switch_management_vlan_id'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('dcn_management_vlan_id'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('dcn_management_vlan_id'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instance_values WHERE subscription_instance_values.resource_type_id IN (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('switch_management_vlan_id', 'dcn_management_vlan_id'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM resource_types WHERE resource_types.resource_type IN ('switch_management_vlan_id', 'dcn_management_vlan_id')
+    """))
diff --git a/gso/oss-params-example.json b/gso/oss-params-example.json
index 63de6f892954672074a3327987572626ed724c16..ac0a29de1cd5204fc2443e2a3e8c4473f52a730b 100644
--- a/gso/oss-params-example.json
+++ b/gso/oss-params-example.json
@@ -51,6 +51,13 @@
       "domain_name": ".geantip",
       "dns_view": "default",
       "network_view": "default"
+    },
+    "LAN_SWITCH_INTERCONNECT": {
+      "V4": {"containers":  ["10.2.0.0/16"], "networks":  [], "mask": 24},
+      "V6": {"containers":  ["beef:cafe::/56"], "networks": [], "mask": 64},
+      "domain_name": ".geant.net",
+      "dns_view": "default",
+      "network_view": "default"
     }
   },
   "MONITORING": {
diff --git a/gso/products/__init__.py b/gso/products/__init__.py
index 88552bc719940b2a95aaed3421c86d1f322566b6..f614447707e9180b18e74d1e3e530c8fd0c04c57 100644
--- a/gso/products/__init__.py
+++ b/gso/products/__init__.py
@@ -1,8 +1,8 @@
 """Module that updates the domain model of GSO. Should contain all types of subscriptions.
 
-.. warning::
-   Whenever a new product is added, this should be reflected in the `ProductType` enumerator.
-   This does not hold for adding a new type of already existing product.
+!!! warning
+    Whenever a new product is added, this should be reflected in the `ProductType` enumerator.
+    This does not hold for adding a new type of already existing product.
 """
 
 from orchestrator.domain import SUBSCRIPTION_MODEL_REGISTRY
diff --git a/gso/products/product_blocks/bgp_session.py b/gso/products/product_blocks/bgp_session.py
index 8113654ae2304be9ee1d265630925508b8eb1be3..863a2da0ac904a5522b58fb15e048c9ee013173b 100644
--- a/gso/products/product_blocks/bgp_session.py
+++ b/gso/products/product_blocks/bgp_session.py
@@ -14,9 +14,13 @@ class IPFamily(strEnum):
     """Possible IP families of a BGP peering."""
 
     V4UNICAST = "ipv4"
+    """Unicast IPv4"""
     V6UNICAST = "ipv6"
+    """Unicast IPv6"""
     V4MULTICAST = "mcast-ipv4"
+    """Multicast IPv4"""
     V6MULTICAST = "mcast-ipv6"
+    """Multicast IPv6"""
 
 
 @strawberry.enum
@@ -24,11 +28,13 @@ class IPTypes(strEnum):
     """Possible IP types of a BGP peering."""
 
     IPV4 = "ipv4"
+    """IPv4"""
     IPV6 = "ipv6"
+    """IPv6"""
 
 
 class BGPSessionInactive(ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="BGPSession"):
-    """A BGP session that is currently inactive. See ``BGPSession``."""
+    """A BGP session that is currently inactive. See `BGPSession`."""
 
     peer_address: IPAddress | None = None
     families: list[IPFamily] = Field(default_factory=list)
@@ -45,7 +51,7 @@ class BGPSessionInactive(ProductBlockModel, lifecycle=[SubscriptionLifecycle.INI
 
 
 class BGPSessionProvisioning(BGPSessionInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
-    """A BGP session that is currently being provisioned. See ``BGPSession``."""
+    """A BGP session that is currently being provisioned. See `BGPSession`."""
 
     peer_address: IPAddress
     families: list[IPFamily]
@@ -62,29 +68,32 @@ class BGPSessionProvisioning(BGPSessionInactive, lifecycle=[SubscriptionLifecycl
 
 
 class BGPSession(BGPSessionProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """A BGP session that is currently deployed in the network."""
+    """A BGP session that is currently deployed in the network.
+
+    Attributes:
+        peer_address: The peering address of the session.
+        families: The list of IP families enabled for this session.
+        has_custom_policies: Whether any custom policies exist for this session.
+        authentication_key: The authentication key of the BGP session.
+        multipath_enabled: Whether multi-path is enabled.
+        send_default_route: Whether we send a last resort route.
+        is_multi_hop: Whether this session is multi-hop or not. Defaults to no.
+        is_passive: Whether this is a passive session.
+        rtbh_enabled: Whether Remote Triggered Blackhole is enabled.
+        bfd_enabled: Settings for BFD.
+        ip_type: The IP type of the session.
+        prefix_limit: A prefix limit, if required.
+    """
 
-    #: The peering address of the session.
     peer_address: IPAddress
-    #: The list of IP families enabled for this session.
     families: list[IPFamily]
-    #: Whether any custom policies exist for this session.
     has_custom_policies: bool
-    #: The authentication key of the BGP session.
     authentication_key: str | None
-    #: Whether multi-path is enabled.
     multipath_enabled: bool
-    #: Whether we send a last resort route.
     send_default_route: bool
-    #: Whether this session is multi-hop or not. Defaults to no.
     is_multi_hop: bool
-    #: Whether this is a passive session.
     is_passive: bool
-    #: Whether Remote Triggered Blackhole is enabled
     rtbh_enabled: bool
-    #: Settings for BFD.
     bfd_enabled: bool
-    #: The IP type of the session.
     ip_type: IPTypes
-    #: A prefix limit, if required
     prefix_limit: NonNegativeInt | None
diff --git a/gso/products/product_blocks/edge_port.py b/gso/products/product_blocks/edge_port.py
index b3400e0f5274957f9d0570a42e2420519fd01a9d..d7249d34bbfa6410834a28b0770ce6b1e3c6a08d 100644
--- a/gso/products/product_blocks/edge_port.py
+++ b/gso/products/product_blocks/edge_port.py
@@ -12,16 +12,14 @@ from gso.utils.types.interfaces import LAGMemberList, PhysicalPortCapacity
 
 
 class EncapsulationType(strEnum):
-    """Types of encapsulation for edge ports.
-
-    Null supports a single service on the port.
-    Dot1Q supports multiple services for one customer or services for multiple customers.
-    QinQ expands VLAN space by double-tagging frames.
-    """
+    """Types of encapsulation for edge ports."""
 
     DOT1Q = "dot1q"
+    """Dot1Q supports multiple services for one customer or services for multiple customers."""
     QINQ = "qinq"
+    """QinQ expands VLAN space by double-tagging frames."""
     NULL = "null"
+    """Null supports a single service on the port."""
 
 
 class EdgePortType(strEnum):
@@ -72,7 +70,7 @@ class EdgePortBlockInactive(
     minimum_links: int | None = None
     edge_port_type: EdgePortType | None = None
     ignore_if_down: bool = False
-    geant_ga_id: str | None = None
+    ga_id: str | None = None
     edge_port_ae_members: LAGMemberList[EdgePortAEMemberBlockInactive]
 
 
@@ -89,34 +87,37 @@ class EdgePortBlockProvisioning(EdgePortBlockInactive, lifecycle=[SubscriptionLi
     minimum_links: int | None = None
     edge_port_type: EdgePortType
     ignore_if_down: bool = False
-    geant_ga_id: str | None = None
+    ga_id: str | None = None
     edge_port_ae_members: LAGMemberList[EdgePortAEMemberBlockProvisioning]  # type: ignore[assignment]
 
 
 class EdgePortBlock(EdgePortBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """An edge port that's currently deployed in the network."""
+    """An edge port that's currently deployed in the network.
+
+    Attributes:
+        node: The router that this Edge Port is connected to.
+        edge_port_name: The name of the edge port, in our case, corresponds to the name of the LAG interface.
+        edge_port_description: A description of the edge port.
+        enable_lacp: Indicates whether LACP is enabled for this edge port.
+        encapsulation: The type of encapsulation used on this edge port, by default DOT1Q.
+        mac_address: The MAC address assigned to this edge port, if applicable.
+        member_speed: The speed capacity of each member in the physical port.
+        minimum_links: The minimum number of links required for this edge port.
+        edge_port_type: The type of edge port (e.g., customer, private, public).
+        ignore_if_down: If set to True, the edge port will be ignored if it is down.
+        ga_id: The GEANT GA ID associated with this edge port, if any.
+        edge_port_ae_members: A list of LAG members associated with this edge port.
+    """
 
-    #: The router that this edge port is connected to.
     node: RouterBlock
-    #: The name of the edge port, in our case, corresponds to the name of the LAG interface.
     edge_port_name: str
-    #: A description of the edge port.
     edge_port_description: str | None = None
-    #: Indicates whether LACP is enabled for this edge port.
     enable_lacp: bool
-    #: The type of encapsulation used on this edge port, by default DOT1Q.
     encapsulation: EncapsulationType = EncapsulationType.DOT1Q
-    #: The MAC address assigned to this edge port, if applicable.
     mac_address: str | None = None
-    #: The speed capacity of each member in the physical port.
     member_speed: PhysicalPortCapacity
-    #: The minimum number of links required for this edge port.
     minimum_links: int | None = None
-    #: The type of edge port (e.g., customer, private, public).
     edge_port_type: EdgePortType
-    #: If set to True, the edge port will be ignored if it is down.
     ignore_if_down: bool = False
-    #: The GEANT GA ID associated with this edge port, if any.
-    geant_ga_id: str | None = None
-    #: A list of LAG members associated with this edge port.
+    ga_id: str | None = None
     edge_port_ae_members: LAGMemberList[EdgePortAEMemberBlock]  # type: ignore[assignment]
diff --git a/gso/products/product_blocks/iptrunk.py b/gso/products/product_blocks/iptrunk.py
index e68911dd1c3b93502ee79d5015e380860b542c72..785d0550a009e71d13cdaa013a224c774b4be0c7 100644
--- a/gso/products/product_blocks/iptrunk.py
+++ b/gso/products/product_blocks/iptrunk.py
@@ -67,7 +67,7 @@ class IptrunkSideBlockInactive(
 
     iptrunk_side_node: RouterBlockInactive
     iptrunk_side_ae_iface: str | None = None
-    iptrunk_side_ae_geant_a_sid: str | None = None
+    ga_id: str | None = None
     iptrunk_side_ae_members: LAGMemberList[IptrunkInterfaceBlockInactive]
 
 
@@ -76,7 +76,7 @@ class IptrunkSideBlockProvisioning(IptrunkSideBlockInactive, lifecycle=[Subscrip
 
     iptrunk_side_node: RouterBlockProvisioning
     iptrunk_side_ae_iface: str
-    iptrunk_side_ae_geant_a_sid: str | None = None
+    ga_id: str | None = None
     iptrunk_side_ae_members: LAGMemberList[IptrunkInterfaceBlockProvisioning]  # type: ignore[assignment]
 
 
@@ -85,7 +85,7 @@ class IptrunkSideBlock(IptrunkSideBlockProvisioning, lifecycle=[SubscriptionLife
 
     iptrunk_side_node: RouterBlock
     iptrunk_side_ae_iface: str
-    iptrunk_side_ae_geant_a_sid: str | None = None
+    ga_id: str | None = None
     iptrunk_side_ae_members: LAGMemberList[IptrunkInterfaceBlock]  # type: ignore[assignment]
 
 
@@ -94,9 +94,9 @@ class IptrunkBlockInactive(
     lifecycle=[SubscriptionLifecycle.INITIAL],
     product_block_name="IptrunkBlock",
 ):
-    """A trunk that's currently inactive, see ``IptrunkBlock``."""
+    """A trunk that's currently inactive, see `IptrunkBlock`."""
 
-    geant_s_sid: str | None = None
+    gs_id: str | None = None
     iptrunk_description: str | None = None
     iptrunk_type: IptrunkType | None = None
     iptrunk_speed: PhysicalPortCapacity | None = None
@@ -108,9 +108,9 @@ class IptrunkBlockInactive(
 
 
 class IptrunkBlockProvisioning(IptrunkBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
-    """A trunk that's currently being provisioned, see ``IptrunkBlock``."""
+    """A trunk that's currently being provisioned, see `IptrunkBlock`."""
 
-    geant_s_sid: str | None = None
+    gs_id: str | None = None
     iptrunk_description: str | None = None
     iptrunk_type: IptrunkType | None = None
     iptrunk_speed: PhysicalPortCapacity | None = None
@@ -122,23 +122,26 @@ class IptrunkBlockProvisioning(IptrunkBlockInactive, lifecycle=[SubscriptionLife
 
 
 class IptrunkBlock(IptrunkBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """A trunk that's currently deployed in the network."""
-
-    #:  GÉANT service ID associated with this trunk.
-    geant_s_sid: str | None = None
-    #:  A human-readable description of this trunk.
+    """A trunk that's currently deployed in the network.
+
+    Attributes:
+        gs_id: GÉANT service ID associated with this trunk.
+        iptrunk_description: A human-readable description of this trunk.
+        iptrunk_type: The type of trunk, can be either dark fibre or leased capacity.
+        iptrunk_speed: The speed of the trunk, measured per interface associated with it.
+        iptrunk_minimum_links: The minimum amount of links the trunk should consist of.
+        iptrunk_isis_metric: The ISIS metric of this link.
+        iptrunk_ipv4_network: The IPv4 network used for this trunk.
+        iptrunk_ipv6_network: The IPv6 network used for this trunk.
+        iptrunk_sides: The two sides that the trunk is connected to.
+    """
+
+    gs_id: str | None = None
     iptrunk_description: str | None = None
-    #:  The type of trunk, can be either dark fibre or leased capacity.
     iptrunk_type: IptrunkType
-    #:  The speed of the trunk, measured per interface associated with it.
     iptrunk_speed: PhysicalPortCapacity
-    #:  The minimum amount of links the trunk should consist of.
     iptrunk_minimum_links: int
-    #:  The ISIS metric of this link
     iptrunk_isis_metric: int
-    #:  The IPv4 network used for this trunk.
     iptrunk_ipv4_network: ipaddress.IPv4Network
-    #:  The IPv6 network used for this trunk.
     iptrunk_ipv6_network: ipaddress.IPv6Network
-    #:  The two sides that the trunk is connected to.
     iptrunk_sides: IptrunkSides[IptrunkSideBlock]  # type: ignore[assignment]
diff --git a/gso/products/product_blocks/l3_core_service.py b/gso/products/product_blocks/l3_core_service.py
index 129b94778d5a170e6869099aa4434629e82ce335..ce2db1782410311e8d37f34d4b92cd27abed8d01 100644
--- a/gso/products/product_blocks/l3_core_service.py
+++ b/gso/products/product_blocks/l3_core_service.py
@@ -27,11 +27,14 @@ class AccessPortProvisioning(AccessPortInactive, lifecycle=[SubscriptionLifecycl
 
 
 class AccessPort(AccessPortProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """An access port for an R&E service."""
+    """An access port for an R&E service.
+
+    Attributes:
+        ap_type: The type of Access Port
+        sbp: The corresponding SBP of this Access Port.
+    """
 
-    #: The type of Access Port
     ap_type: APType
-    #: The corresponding SBP of this Access Port.
     sbp: ServiceBindingPort
 
 
@@ -50,7 +53,10 @@ class L3CoreServiceBlockProvisioning(L3CoreServiceBlockInactive, lifecycle=[Subs
 
 
 class L3CoreServiceBlock(L3CoreServiceBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """An active L3 Core Service subscription block."""
+    """An active L3 Core Service subscription block.
+
+    Attributes:
+        ap_list: The list of Access Points where this service is present.
+    """
 
-    #: The list of Access Points where this service is present.
     ap_list: list[AccessPort]  # type: ignore[assignment]
diff --git a/gso/products/product_blocks/lan_switch_interconnect.py b/gso/products/product_blocks/lan_switch_interconnect.py
index a3128a72347e90d8f5eb86d00509e441b97f2b01..624f40152f747c61911e06a57cdbb50ef26dcc75 100644
--- a/gso/products/product_blocks/lan_switch_interconnect.py
+++ b/gso/products/product_blocks/lan_switch_interconnect.py
@@ -6,7 +6,7 @@ from orchestrator.types import SubscriptionLifecycle
 from gso.products.product_blocks.router import RouterBlock, RouterBlockInactive, RouterBlockProvisioning
 from gso.products.product_blocks.switch import SwitchBlock, SwitchBlockInactive, SwitchBlockProvisioning
 from gso.utils.types.interfaces import LAGMemberList
-from gso.utils.types.ip_address import AddressSpace, IPv4AddressType, IPv4NetworkType
+from gso.utils.types.virtual_identifiers import VLAN_ID
 
 
 class LanSwitchInterconnectInterfaceBlockInactive(
@@ -48,7 +48,6 @@ class LanSwitchInterconnectRouterSideBlockInactive(
     node: RouterBlockInactive
     ae_iface: str | None = None
     ae_members: LAGMemberList[LanSwitchInterconnectInterfaceBlockInactive]
-    ipv4_address: IPv4AddressType | None = None
 
 
 class LanSwitchInterconnectRouterSideBlockProvisioning(
@@ -59,7 +58,6 @@ class LanSwitchInterconnectRouterSideBlockProvisioning(
     node: RouterBlockProvisioning
     ae_iface: str | None = None
     ae_members: LAGMemberList[LanSwitchInterconnectInterfaceBlockProvisioning]  # type: ignore[assignment]
-    ipv4_address: IPv4AddressType | None
 
 
 class LanSwitchInterconnectRouterSideBlock(
@@ -70,7 +68,6 @@ class LanSwitchInterconnectRouterSideBlock(
     node: RouterBlock
     ae_iface: str
     ae_members: LAGMemberList[LanSwitchInterconnectInterfaceBlock]  # type: ignore[assignment]
-    ipv4_address: IPv4AddressType | None
 
 
 class LanSwitchInterconnectSwitchSideBlockInactive(
@@ -83,7 +80,6 @@ class LanSwitchInterconnectSwitchSideBlockInactive(
     switch: SwitchBlockInactive
     ae_iface: str | None = None
     ae_members: LAGMemberList[LanSwitchInterconnectInterfaceBlockInactive]
-    ipv4_address: IPv4AddressType | None = None
 
 
 class LanSwitchInterconnectSwitchSideBlockProvisioning(
@@ -94,7 +90,6 @@ class LanSwitchInterconnectSwitchSideBlockProvisioning(
     switch: SwitchBlockProvisioning
     ae_iface: str | None = None
     ae_members: LAGMemberList[LanSwitchInterconnectInterfaceBlockProvisioning]  # type: ignore[assignment]
-    ipv4_address: IPv4AddressType | None
 
 
 class LanSwitchInterconnectSwitchSideBlock(
@@ -105,7 +100,6 @@ class LanSwitchInterconnectSwitchSideBlock(
     switch: SwitchBlock
     ae_iface: str
     ae_members: LAGMemberList[LanSwitchInterconnectInterfaceBlock]  # type: ignore[assignment]
-    ipv4_address: IPv4AddressType | None
 
 
 class LanSwitchInterconnectBlockInactive(
@@ -116,9 +110,9 @@ class LanSwitchInterconnectBlockInactive(
     """A LAN Switch Interconnect that's currently inactive, see ``LanSwitchInterconnectBlock``."""
 
     lan_switch_interconnect_description: str | None = None
-    lan_switch_interconnect_ip_network: IPv4NetworkType | None = None
-    address_space: AddressSpace | None = None
     minimum_links: int | None = None
+    switch_management_vlan_id: VLAN_ID | None = None
+    dcn_management_vlan_id: VLAN_ID | None = None
     router_side: LanSwitchInterconnectRouterSideBlockInactive
     switch_side: LanSwitchInterconnectSwitchSideBlockInactive
 
@@ -129,25 +123,29 @@ class LanSwitchInterconnectBlockProvisioning(
     """A LAN Switch Interconnect that's currently being provisioned, see ``LanSwitchInterconnectBlock``."""
 
     lan_switch_interconnect_description: str | None = None
-    lan_switch_interconnect_ip_network: IPv4NetworkType | None
-    address_space: AddressSpace | None = None
     minimum_links: int | None = None
+    switch_management_vlan_id: VLAN_ID
+    dcn_management_vlan_id: VLAN_ID | None
     router_side: LanSwitchInterconnectRouterSideBlockProvisioning
     switch_side: LanSwitchInterconnectSwitchSideBlockProvisioning
 
 
 class LanSwitchInterconnectBlock(LanSwitchInterconnectBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """A LAN Switch Interconnect that's currently deployed in the network."""
+    """A LAN Switch Interconnect that's currently deployed in the network.
+
+    Attributes:
+        lan_switch_interconnect_description: A human-readable description of this LAN Switch Interconnect.
+        minimum_links: The minimum amount of links the LAN Switch Interconnect should consist of.
+        switch_management_vlan_id: VLAN ID for the switch management network.
+        dcn_management_vlan_id: VLAN ID for the DCN management network, if the site of this product contains optical
+            equipment.
+        router_side: The router side of the LAN Switch Interconnect.
+        switch_side: The switch side of the LAN Switch Interconnect.
+    """
 
-    #: A human-readable description of this LAN Switch Interconnect.
     lan_switch_interconnect_description: str
-    #: The IP resources for this VLAN Switch Interconnect.
-    lan_switch_interconnect_ip_network: IPv4NetworkType | None
-    #: The address space of the VLAN Switch Interconnect. It can be private or public.
-    address_space: AddressSpace
-    #: The minimum amount of links the LAN Switch Interconnect should consist of.
     minimum_links: int
-    #: The router side of the LAN Switch Interconnect.
+    switch_management_vlan_id: VLAN_ID
+    dcn_management_vlan_id: VLAN_ID | None
     router_side: LanSwitchInterconnectRouterSideBlock
-    #: The switch side of the LAN Switch Interconnect.
     switch_side: LanSwitchInterconnectSwitchSideBlock
diff --git a/gso/products/product_blocks/layer_2_circuit.py b/gso/products/product_blocks/layer_2_circuit.py
index 40968dfea10be3dc5ab4204fe7dd258c3a8f2194..2e1a7c53b7a995efedc6c09377ef19f4d2ee158b 100644
--- a/gso/products/product_blocks/layer_2_circuit.py
+++ b/gso/products/product_blocks/layer_2_circuit.py
@@ -95,21 +95,24 @@ class Layer2CircuitBlockProvisioning(Layer2CircuitBlockInactive, lifecycle=[Subs
 
 
 class Layer2CircuitBlock(Layer2CircuitBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """An active Layer 2 Circuit."""
+    """An active Layer 2 Circuit.
+
+    Attributes:
+        layer_2_circuit_sides: The two sides that the Layer 2 Circuit is connected to.
+        virtual_circuit_id: Virtual Circuit ID of this Layer 2 Circuit.
+        layer_2_circuit_type: The type of circuit, can be tagged or untagged.
+        vlan_range_lower_bound: If tagged, the lower and upper bounds will set the VLAN range.
+        vlan_range_upper_bound: Lower and Upper bounds are including.
+        policer_enabled: Whether this Layer 2 Circuit is policed.
+        policer_burst_rate: If policed, the burst rate of the policer.
+        bandwidth: If policed, the bandwidth of the policer is stored.
+    """
 
-    #: The two sides that the Layer 2 Circuit is connected to.
     layer_2_circuit_sides: Layer2CircuitSides[Layer2CircuitSideBlock]
-    #: Virtual Circuit ID of this Layer 2 Circuit.
     virtual_circuit_id: VC_ID
-    #: The type of circuit, can be tagged or untagged.
     layer_2_circuit_type: Layer2CircuitType
-    #: If tagged, the lower and upper bounds will set the VLAN range.
     vlan_range_lower_bound: VLAN_ID | None
-    #: Lower and Upper bounds are including.
     vlan_range_upper_bound: VLAN_ID | None
-    #: Whether this Layer 2 Circuit is policed.
     policer_enabled: bool
-    #: If policed, the burst rate of the policer
     policer_burst_rate: BandwidthString | None
-    #: If policed, the bandwidth of the policer is stored.
     bandwidth: BandwidthString | None
diff --git a/gso/products/product_blocks/office_router.py b/gso/products/product_blocks/office_router.py
index 0a16b4f43a27b385b9b3dc051feaf4fcf626ad78..8afb980971849a40619ac5a357e2e05f0bbf4673 100644
--- a/gso/products/product_blocks/office_router.py
+++ b/gso/products/product_blocks/office_router.py
@@ -17,39 +17,43 @@ class OfficeRouterBlockInactive(
     lifecycle=[SubscriptionLifecycle.INITIAL],
     product_block_name="OfficeRouterBlock",
 ):
-    """An office router that's being currently inactive. See ``OfficeRouterBlock``."""
+    """An office router that's being currently inactive. See `OfficeRouterBlock`."""
 
     office_router_fqdn: str | None = None
     office_router_ts_port: PortNumber | None = None
     office_router_lo_ipv4_address: IPv4AddressType | None = None
     office_router_lo_ipv6_address: IPv6AddressType | None = None
     office_router_site: SiteBlockInactive | None
-    vendor: Vendor | None = None
+    vendor: Vendor | None = Vendor.JUNIPER
 
 
 class OfficeRouterBlockProvisioning(OfficeRouterBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
-    """An office router that's being provisioned. See ``RouterBlock``."""
+    """An office router that's being provisioned. See `RouterBlock`."""
 
-    office_router_fqdn: str | None = None
-    office_router_ts_port: PortNumber | None = None
-    office_router_lo_ipv4_address: IPv4AddressType | None = None
-    office_router_lo_ipv6_address: IPv6AddressType | None = None
+    office_router_fqdn: str | None
+    office_router_ts_port: PortNumber | None
+    office_router_lo_ipv4_address: IPv4AddressType | None
+    office_router_lo_ipv6_address: IPv6AddressType | None
     office_router_site: SiteBlockProvisioning | None
-    vendor: Vendor | None = None
+    vendor: Vendor
 
 
 class OfficeRouterBlock(OfficeRouterBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """An office router that's currently deployed in the network."""
+    """An office router that's currently deployed in the network.
+
+    Attributes:
+        office_router_fqdn: Office router FQDN.
+        office_router_ts_port: The port of the terminal server that this office router is connected to. Used to offer
+            out of band access.
+        office_router_lo_ipv4_address: The IPv4 loopback address of the office router.
+        office_router_lo_ipv6_address: The IPv6 loopback address of the office router.
+        office_router_site: The `Site` that this office router resides in. Both physically and computationally.
+        vendor: The vendor of an office router. Defaults to Juniper.
+    """
 
-    #:  Office router FQDN.
     office_router_fqdn: str
-    #:  The port of the terminal server that this office router is connected to. Used to offer out of band access.
     office_router_ts_port: PortNumber
-    #:  The IPv4 loopback address of the office router.
     office_router_lo_ipv4_address: IPv4AddressType
-    #:  The IPv6 loopback address of the office router.
     office_router_lo_ipv6_address: IPv6AddressType
-    #:  The ``Site`` that this office router resides in. Both physically and computationally.
     office_router_site: SiteBlock
-    #:  The vendor of an office router. Defaults to Juniper.
-    vendor: Vendor = Vendor.JUNIPER
+    vendor: Vendor
diff --git a/gso/products/product_blocks/opengear.py b/gso/products/product_blocks/opengear.py
index 8fd88b31718c5cf255462a3af45be586ca0e6497..0daca4899edda40c51fe39a926284e83d007c918 100644
--- a/gso/products/product_blocks/opengear.py
+++ b/gso/products/product_blocks/opengear.py
@@ -1,4 +1,4 @@
-"""Product block for ``Opengear`` products."""
+"""Product block for `Opengear` products."""
 
 import ipaddress
 
@@ -17,7 +17,7 @@ class OpengearBlockInactive(
     lifecycle=[SubscriptionLifecycle.INITIAL],
     product_block_name="OpengearBlock",
 ):
-    """An Opengear that's being currently inactive. See ``OpengearBlock``."""
+    """An Opengear that's being currently inactive. See `OpengearBlock`."""
 
     opengear_hostname: str | None = None
     opengear_site: SiteBlockInactive | None = None
@@ -27,25 +27,28 @@ class OpengearBlockInactive(
 
 
 class OpengearBlockProvisioning(OpengearBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
-    """An Opengear that's being provisioned. See ``OpengearBlock``."""
+    """An Opengear that's being provisioned. See `OpengearBlock`."""
 
     opengear_hostname: str
     opengear_site: SiteBlockProvisioning
-    opengear_wan_address: ipaddress.IPv4Address | None = None
-    opengear_wan_netmask: ipaddress.IPv4Address | None = None
-    opengear_wan_gateway: ipaddress.IPv4Address | None = None
+    opengear_wan_address: ipaddress.IPv4Address | None
+    opengear_wan_netmask: ipaddress.IPv4Address | None
+    opengear_wan_gateway: ipaddress.IPv4Address | None
 
 
 class OpengearBlock(OpengearBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """An Opengear that's currently deployed in the network."""
+    """An Opengear that's currently deployed in the network.
+
+    Attributes:
+        opengear_hostname: The hostname of the Opengear device.
+        opengear_site: The site where the Opengear device is located.
+        opengear_wan_address: The WAN address of the Opengear device.
+        opengear_wan_netmask: The WAN netmask of the Opengear device.
+        opengear_wan_gateway: The WAN gateway of the Opengear device.
+    """
 
-    #: The hostname of the Opengear device.
     opengear_hostname: str
-    #: The site where the Opengear device is located.
     opengear_site: SiteBlock
-    #: The WAN address of the Opengear device.
     opengear_wan_address: ipaddress.IPv4Address
-    #: The WAN netmask of the Opengear device.
     opengear_wan_netmask: ipaddress.IPv4Address
-    #: The WAN gateway of the Opengear device.
     opengear_wan_gateway: ipaddress.IPv4Address
diff --git a/gso/products/product_blocks/pop_vlan.py b/gso/products/product_blocks/pop_vlan.py
index f4ee9c4030171932fef89e2c1c0d7e2a3511de25..a26e072d11089a0ecadbcfbc7b7cb75cdedffde7 100644
--- a/gso/products/product_blocks/pop_vlan.py
+++ b/gso/products/product_blocks/pop_vlan.py
@@ -60,7 +60,7 @@ class PopVlanBlockInactive(
     lifecycle=[SubscriptionLifecycle.INITIAL],
     product_block_name="PopVlanBlock",
 ):
-    """A PoP VLAN that's currently inactive, see ``PopVlanBlock``."""
+    """A PoP VLAN that's currently inactive, see `PopVlanBlock`."""
 
     vlan_id: int
     pop_vlan_description: str | None = None
@@ -72,7 +72,7 @@ class PopVlanBlockInactive(
 
 
 class PopVlanBlockProvisioning(PopVlanBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
-    """A Pop VLAN that's currently being provisioned, see ``PopVlanBlock``."""
+    """A Pop VLAN that's currently being provisioned, see `PopVlanBlock`."""
 
     vlan_id: int
     pop_vlan_description: str | None
@@ -84,19 +84,22 @@ class PopVlanBlockProvisioning(PopVlanBlockInactive, lifecycle=[SubscriptionLife
 
 
 class PopVlanBlock(PopVlanBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """A Pop VLAN that's currently deployed in the network."""
+    """A Pop VLAN that's currently deployed in the network.
+
+    Attributes:
+        vlan_id: The VLAN ID of the Pop VLAN.
+        pop_vlan_description: The description of the Pop VLAN.
+        lan_switch_interconnect: The LAN Switch Interconnect that this Pop VLAN is connected to.
+        ports: The ports of the Pop VLAN.
+        layer_preference: The level of the layer preference for the Pop VLAN (L2 or L3).
+        ipv4_network: IPv4 network for the Pop VLAN if layer preference is L3.
+        ipv6_network: IPv6 network for the Pop VLAN if layer preference is L3.
+    """
 
-    #: The VLAN ID of the Pop VLAN.
     vlan_id: int
-    #: The description of the Pop VLAN.
     pop_vlan_description: str
-    #: The LAN Switch Interconnect that this Pop VLAN is connected to.
     lan_switch_interconnect: LanSwitchInterconnectBlock
-    #: The ports of the Pop VLAN.
     ports: PortList[PopVlanPortBlock]  # type: ignore[assignment]
-    #: The level of the layer preference for the Pop VLAN (L2 or L3).
     layer_preference: LayerPreference
-    #: IPv4 network for the Pop VLAN if layer preference is L3.
     ipv4_network: IPv4Network | None
-    #: IPv6 network for the Pop VLAN if layer preference is L3.
     ipv6_network: IPv6Network | None
diff --git a/gso/products/product_blocks/router.py b/gso/products/product_blocks/router.py
index 5ffc2ef98cb1a10f22e27b9d52d61e941fb2715a..d31d2109aae8b2bc95c876777faa53c46c668d83 100644
--- a/gso/products/product_blocks/router.py
+++ b/gso/products/product_blocks/router.py
@@ -1,4 +1,4 @@
-"""Product block for ``Router`` products."""
+"""Product block for `Router` products."""
 
 from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle, strEnum
@@ -25,7 +25,7 @@ class RouterBlockInactive(
     lifecycle=[SubscriptionLifecycle.INITIAL],
     product_block_name="RouterBlock",
 ):
-    """A router that's being currently inactive. See ``RouterBlock``."""
+    """A router that's being currently inactive. See `RouterBlock`."""
 
     router_fqdn: str | None = None
     router_ts_port: PortNumber | None = None
@@ -39,7 +39,7 @@ class RouterBlockInactive(
 
 
 class RouterBlockProvisioning(RouterBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
-    """A router that's being provisioned. See ``RouterBlock``."""
+    """A router that's being provisioned. See `RouterBlock`."""
 
     router_fqdn: str
     router_ts_port: PortNumber
@@ -53,23 +53,28 @@ class RouterBlockProvisioning(RouterBlockInactive, lifecycle=[SubscriptionLifecy
 
 
 class RouterBlock(RouterBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """A router that's currently deployed in the network."""
+    """A router that's currently deployed in the network.
+
+    Attributes:
+        router_fqdn: FQDN of a router.
+        router_ts_port: The port of the terminal server that this router is connected to. Used to offer out of band
+            access.
+        router_access_via_ts: Whether this router should be accessed through the terminal server, or through its
+            loopback address.
+        router_lo_ipv4_address: The IPv4 loopback address of the router.
+        router_lo_ipv6_address: The IPv6 loopback address of the router.
+        router_lo_iso_address: The ISO NET of the router, used for ISIS support.
+        router_role: The role of the router, which can be any of the values defined in `RouterRole`.
+        router_site: The site that this router resides in. Both physically and computationally.
+        vendor: The vendor of a router.
+    """
 
-    #:  FQDN of a router.
     router_fqdn: str
-    #:  The port of the terminal server that this router is connected to. Used to offer out of band access.
     router_ts_port: PortNumber
-    #:  Whether this router should be accessed through the terminal server, or through its loopback address.
     router_access_via_ts: bool
-    #:  The IPv4 loopback address of the router.
     router_lo_ipv4_address: IPv4AddressType
-    #:  The IPv6 loopback address of the router.
     router_lo_ipv6_address: IPv6AddressType
-    #:  The ISO NET of the router, used for ISIS support.
     router_lo_iso_address: str
-    #:  The role of the router, which can be any of the values defined in ``RouterRole``.
     router_role: RouterRole
-    #:  The ``Site`` that this router resides in. Both physically and computationally.
     router_site: SiteBlock
-    #:  The vendor of a router.
     vendor: Vendor
diff --git a/gso/products/product_blocks/service_binding_port.py b/gso/products/product_blocks/service_binding_port.py
index 6fd7467c95808ee42438f2af74a1e0ba05a1609e..5eaec9af013f851496d3c473a81cac5331013511 100644
--- a/gso/products/product_blocks/service_binding_port.py
+++ b/gso/products/product_blocks/service_binding_port.py
@@ -14,14 +14,14 @@ from gso.products.product_blocks.bgp_session import (
 )
 from gso.products.product_blocks.edge_port import EdgePortBlock, EdgePortBlockInactive, EdgePortBlockProvisioning
 from gso.utils.shared_enums import SBPType
-from gso.utils.types.ip_address import IPv4AddressType, IPV4Netmask, IPv6AddressType, IPV6Netmask
+from gso.utils.types.ip_address import IPv4AddressType, IPv4Netmask, IPv6AddressType, IPv6Netmask
 from gso.utils.types.virtual_identifiers import VLAN_ID
 
 
 class BFDSettingsInactive(
     ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="BFDSettings"
 ):
-    """Settings for BFD, see ``BFDSettings``."""
+    """Settings for BFD, see `BFDSettings`."""
 
     bfd_enabled: bool = False
     bfd_interval_rx: int | None = None
@@ -30,7 +30,7 @@ class BFDSettingsInactive(
 
 
 class BFDSettingsProvisioning(BFDSettingsInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
-    """Settings for BFD, see ``BFDSettings``."""
+    """Settings for BFD, see `BFDSettings`."""
 
     bfd_enabled: bool
     bfd_interval_rx: int | None = None
@@ -39,32 +39,35 @@ class BFDSettingsProvisioning(BFDSettingsInactive, lifecycle=[SubscriptionLifecy
 
 
 class BFDSettings(BFDSettingsProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """A set of settings for BFD."""
+    """A set of settings for BFD.
+
+    Attributes:
+        bfd_enabled: Whether BFD is enabled.
+        bfd_interval_rx: The BFD interval RX, if enabled.
+        bfd_interval_tx: The BFD interval TX, if enabled.
+        bfd_multiplier: The BFD multiplier, if enabled.
+    """
 
-    #: Whether BFD is enabled.
     bfd_enabled: bool
-    #: The BFD interval RX, if enabled.
     bfd_interval_rx: int | None
-    #: The BFD interval TX, if enabled.
     bfd_interval_tx: int | None
-    #: The BFD multiplier, if enabled.
     bfd_multiplier: int | None
 
 
 class ServiceBindingPortInactive(
     ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="ServiceBindingPort"
 ):
-    """A Service Binding Port that's currently inactive. See ``ServiceBindingPort``."""
+    """A Service Binding Port that's currently inactive. See `ServiceBindingPort`."""
 
     is_tagged: bool | None = None
     vlan_id: VLAN_ID | None = None
     sbp_type: SBPType | None = None
     ipv4_address: IPv4AddressType | None = None
-    ipv4_mask: IPV4Netmask | None = None
+    ipv4_mask: IPv4Netmask | None = None
     ipv6_address: IPv6AddressType | None = None
-    ipv6_mask: IPV6Netmask | None = None
+    ipv6_mask: IPv6Netmask | None = None
     custom_firewall_filters: bool | None = None
-    geant_sid: str | None = None
+    gs_id: str | None = None
     bgp_session_list: list[BGPSessionInactive] = Field(default_factory=list)
     edge_port: EdgePortBlockInactive | None = None
     v4_bfd_settings: BFDSettingsInactive
@@ -72,17 +75,17 @@ class ServiceBindingPortInactive(
 
 
 class ServiceBindingPortProvisioning(ServiceBindingPortInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
-    """A Service Binding Port that's currently being provisioned. See ``ServiceBindingPort``."""
+    """A Service Binding Port that's currently being provisioned. See `ServiceBindingPort`."""
 
     is_tagged: bool
     vlan_id: VLAN_ID | None = None
     sbp_type: SBPType
     ipv4_address: IPv4AddressType | None = None
-    ipv4_mask: IPV4Netmask | None = None
+    ipv4_mask: IPv4Netmask | None = None
     ipv6_address: IPv6AddressType | None = None
-    ipv6_mask: IPV6Netmask | None = None
+    ipv6_mask: IPv6Netmask | None = None
     custom_firewall_filters: bool
-    geant_sid: str
+    gs_id: str
     bgp_session_list: list[BGPSessionProvisioning]  # type: ignore[assignment]
     edge_port: EdgePortBlockProvisioning
     v4_bfd_settings: BFDSettingsProvisioning
@@ -90,31 +93,34 @@ class ServiceBindingPortProvisioning(ServiceBindingPortInactive, lifecycle=[Subs
 
 
 class ServiceBindingPort(ServiceBindingPortProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """A service binding port that is actively used in the network."""
+    """A service binding port that is actively used in the network.
+
+    Attributes:
+        is_tagged: Whether this VLAN is tagged or not.
+        vlan_id: The VLAN ID.
+        sbp_type: Is this service binding port layer 2 or 3?
+        ipv4_address: If layer 3, IPv4 resources.
+        ipv4_mask: IPv4 subnet mask.
+        ipv6_address: If layer 3, IPv6 resources.
+        ipv6_mask: IPv6 subnet mask.
+        custom_firewall_filters: Any custom firewall filters that the partner may require.
+        gs_id: The GÉANT service ID of this binding port.
+        bgp_session_list: The BGP sessions associated with this service binding port.
+        edge_port: The Edge Port on which this SBP resides.
+        v4_bfd_settings: BFD settings for IPv4.
+        v6_bfd_settings: BFD settings for IPv6.
+    """
 
-    #: Whether this VLAN is tagged or not.
     is_tagged: bool
-    #: The VLAN ID.
     vlan_id: VLAN_ID | None = None
-    #: Is this service binding port layer 2 or 3?
     sbp_type: SBPType
-    #: If layer 3, IPv4 resources.
     ipv4_address: IPv4AddressType | None = None
-    #: IPV4 subnet mask.
-    ipv4_mask: IPV4Netmask | None = None
-    #: If layer 3, IPv6 resources.
+    ipv4_mask: IPv4Netmask | None = None
     ipv6_address: IPv6AddressType | None = None
-    #: IPV6 subnet mask.
-    ipv6_mask: IPV6Netmask | None = None
-    #: Any custom firewall filters that the partner may require.
+    ipv6_mask: IPv6Netmask | None = None
     custom_firewall_filters: bool
-    #: The GÉANT service ID of this binding port.
-    geant_sid: str
-    #: The BGP sessions associated with this service binding port.
+    gs_id: str
     bgp_session_list: list[BGPSession]  # type: ignore[assignment]
-    #: The Edge Port on which this SBP resides.
     edge_port: EdgePortBlock
-    #: BFD settings for IPv4
     v4_bfd_settings: BFDSettings
-    #: BFD settings for IPv6
     v6_bfd_settings: BFDSettings
diff --git a/gso/products/product_blocks/site.py b/gso/products/product_blocks/site.py
index 11c8e3364ddc94582dc637302d042e57c1fe2060..22cc4940094ca50d494bc90507466a57b125e299 100644
--- a/gso/products/product_blocks/site.py
+++ b/gso/products/product_blocks/site.py
@@ -26,7 +26,7 @@ class SiteBlockInactive(
     lifecycle=[SubscriptionLifecycle.INITIAL],
     product_block_name="SiteBlock",
 ):
-    """A site that's currently inactive, see ``SiteBlock``."""
+    """A site that's currently inactive, see `SiteBlock`."""
 
     site_name: SiteName | None = None
     site_city: str | None = None
@@ -38,10 +38,11 @@ class SiteBlockInactive(
     site_bgp_community_id: int | None = None
     site_tier: SiteTier | None = None
     site_ts_address: IPAddress | None = None
+    site_contains_optical_equipment: bool | None = None
 
 
 class SiteBlockProvisioning(SiteBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
-    """A site that's currently being provisioned, see ``SiteBlock``."""
+    """A site that's currently being provisioned, see `SiteBlock`."""
 
     site_name: SiteName
     site_city: str
@@ -53,32 +54,39 @@ class SiteBlockProvisioning(SiteBlockInactive, lifecycle=[SubscriptionLifecycle.
     site_bgp_community_id: int
     site_tier: SiteTier
     site_ts_address: IPAddress
+    site_contains_optical_equipment: bool
 
 
 class SiteBlock(SiteBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """A site that's currently available for routers and services to be hosted at."""
+    """A site that's currently available for routers and services to be hosted at.
+
+    Attributes:
+        site_name: The name of the site, that will dictate part of the FQDN of routers that are hosted at this site. For
+            example: `router.X.Y.geant.net`, where X denotes the name of the site.
+        site_city: The city at which the site is located.
+        site_country: The country in which the site is located.
+        site_country_code: The code of the corresponding country. This is also used for the FQDN, following the example
+            given for the site name, the country code would end up in the Y position.
+        site_latitude: The latitude of the site, used for SNMP purposes.
+        site_longitude: Similar to the latitude, the longitude of a site.
+        site_internal_id: The internal ID used within GÉANT to denote a site.
+        site_bgp_community_id: The BGP community ID of a site, used to advertise routes learned at this site.
+        site_tier: The tier of a site, as described in `SiteTier`.
+        site_ts_address: The address of the terminal server that this router is connected to. The terminal server
+            provides out of band access. This is required in case a link goes down, or when a router is initially added
+            to the network, and it does not have any IP trunks connected to it.
+        site_contains_optical_equipment: Whether this site contains optical equipment, which dictates the need for a DCN
+            management VLAN.
+    """
 
-    #:  The name of the site, that will dictate part of the FQDN of routers that are hosted at this site. For
-    #:  example: ``router.X.Y.geant.net``, where X denotes the name of the site.
     site_name: SiteName
-    #:  The city at which the site is located.
     site_city: str
-    #:  The country in which the site is located.
     site_country: str
-    #:  The code of the corresponding country. This is also used for the FQDN, following the example given for
-    #:  the site name, the country code would end up in the Y position.
     site_country_code: str
-    #:  The latitude of the site, used for SNMP purposes.
     site_latitude: LatitudeCoordinate
-    #:  Similar to the latitude, the longitude of a site.
     site_longitude: LongitudeCoordinate
-    #:  The internal ID used within GÉANT to denote a site.
     site_internal_id: int
-    #:  The BGP community ID of a site, used to advertise routes learned at this site.
     site_bgp_community_id: int
-    #:  The tier of a site, as described in ``SiteTier``.
     site_tier: SiteTier
-    #:  The address of the terminal server that this router is connected to. The terminal server provides out of band
-    #:  access. This is required in case a link goes down, or when a router is initially added to the network and it
-    #:  does not have any IP trunks connected to it.
     site_ts_address: IPAddress
+    site_contains_optical_equipment: bool
diff --git a/gso/products/product_blocks/super_pop_switch.py b/gso/products/product_blocks/super_pop_switch.py
index 63f161921a030a8787d881ac4a02b2032b9c342a..3c436e071b0d0f86d6f5264be637e733532a1b25 100644
--- a/gso/products/product_blocks/super_pop_switch.py
+++ b/gso/products/product_blocks/super_pop_switch.py
@@ -17,35 +17,39 @@ class SuperPopSwitchBlockInactive(
     lifecycle=[SubscriptionLifecycle.INITIAL],
     product_block_name="SuperPopSwitchBlock",
 ):
-    """A Super PoP switch that's being currently inactive. See ``SuperPoPSwitchBlock``."""
+    """A Super PoP switch that's being currently inactive. See `SuperPoPSwitchBlock`."""
 
     super_pop_switch_fqdn: str | None = None
     super_pop_switch_ts_port: PortNumber | None = None
     super_pop_switch_mgmt_ipv4_address: IPv4AddressType | None = None
     super_pop_switch_site: SiteBlockInactive | None
-    vendor: Vendor | None = None
+    vendor: Vendor | None = Vendor.JUNIPER
 
 
 class SuperPopSwitchBlockProvisioning(SuperPopSwitchBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
-    """A Super PoP switch that's being provisioned. See ``SuperPoPSwitchBlock``."""
+    """A Super PoP switch that's being provisioned. See `SuperPoPSwitchBlock`."""
 
-    super_pop_switch_fqdn: str | None = None
-    super_pop_switch_ts_port: PortNumber | None = None
-    super_pop_switch_mgmt_ipv4_address: IPv4AddressType | None = None
+    super_pop_switch_fqdn: str | None
+    super_pop_switch_ts_port: PortNumber | None
+    super_pop_switch_mgmt_ipv4_address: IPv4AddressType | None
     super_pop_switch_site: SiteBlockProvisioning | None
-    vendor: Vendor | None = None
+    vendor: Vendor | None
 
 
 class SuperPopSwitchBlock(SuperPopSwitchBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """A Super PoP switch that's currently deployed in the network."""
+    """A Super PoP switch that's currently deployed in the network.
+
+    Attributes:
+        super_pop_switch_fqdn: Super PoP switch FQDN.
+        super_pop_switch_ts_port: The port of the terminal server that this Super PoP switch is connected to. Used to
+            offer out of band access.
+        super_pop_switch_mgmt_ipv4_address: The IPv4 management address of the Super PoP switch.
+        super_pop_switch_site: The `Site` that this Super PoP switch resides in. Both physically and computationally.
+        vendor: The vendor of a Super PoP switch. Defaults to Juniper.
+    """
 
-    #:  Super PoP switch FQDN.
     super_pop_switch_fqdn: str
-    #:  The port of the terminal server that this Super PoP switch is connected to. Used to offer out of band access.
     super_pop_switch_ts_port: PortNumber
-    #:  The IPv4 management address of the Super PoP switch.
     super_pop_switch_mgmt_ipv4_address: IPv4AddressType
-    #:  The ``Site`` that this Super PoP switch resides in. Both physically and computationally.
     super_pop_switch_site: SiteBlock
-    #:  The vendor of a Super PoP switch. Defaults to Juniper.
-    vendor: Vendor = Vendor.JUNIPER
+    vendor: Vendor
diff --git a/gso/products/product_blocks/switch.py b/gso/products/product_blocks/switch.py
index f6bdb87769022cf8f3d4063e160d761eb888894a..918773269954e9850c314fea88ff8efb2fa1b452 100644
--- a/gso/products/product_blocks/switch.py
+++ b/gso/products/product_blocks/switch.py
@@ -1,4 +1,4 @@
-"""Product block for ``Switch`` products."""
+"""Product block for `Switch` products."""
 
 from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle
@@ -16,7 +16,9 @@ from gso.utils.types.ip_address import PortNumber
 class SwitchModel(strEnum):
     """Enumerator for the different types of switches."""
 
-    EX3400 = "EX3400"
+    EX3400_24T = "EX3400 - 24T"
+    EX3400_48T = "EX3400 - 48T"
+    EX4300_32F = "EX4300 - 32F"
 
 
 class SwitchBlockInactive(
@@ -24,7 +26,7 @@ class SwitchBlockInactive(
     lifecycle=[SubscriptionLifecycle.INITIAL],
     product_block_name="SwitchBlock",
 ):
-    """A switch that's being currently inactive. See ``SwitchBlock``."""
+    """A switch that's being currently inactive. See `SwitchBlock`."""
 
     fqdn: str | None = None
     ts_port: PortNumber | None = None
@@ -34,7 +36,7 @@ class SwitchBlockInactive(
 
 
 class SwitchBlockProvisioning(SwitchBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
-    """A switch that's being provisioned. See ``SwitchBlock``."""
+    """A switch that's being provisioned. See `SwitchBlock`."""
 
     fqdn: str
     ts_port: PortNumber
@@ -44,15 +46,18 @@ class SwitchBlockProvisioning(SwitchBlockInactive, lifecycle=[SubscriptionLifecy
 
 
 class SwitchBlock(SwitchBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """A switch that's currently deployed in the network."""
+    """A switch that's currently deployed in the network.
+
+    Attributes:
+        fqdn: The FQDN of the switch.
+        ts_port: The port of the terminal server that this switch is connected to. Used to offer out of band access.
+        site: The site that this switch resides in. Both physically and computationally.
+        switch_vendor: The vendor of the switch.
+        switch_model: The model of the switch.
+    """
 
-    #: The FQDN of the switch.
     fqdn: str
-    #:  The port of the terminal server that this switch is connected to. Used to offer out of band access.
     ts_port: PortNumber
-    #:  The ``Site`` that this switch resides in. Both physically and computationally.
     site: SiteBlock
-    #: The vendor of the switch.
     switch_vendor: Vendor
-    #: The model of the switch.
     switch_model: SwitchModel
diff --git a/gso/products/product_blocks/vrf.py b/gso/products/product_blocks/vrf.py
index 5070c48a2f0e5f35e0db64c8d4e47f8447aac19d..d53803af4960e07c006f80e65f4411c8767639c7 100644
--- a/gso/products/product_blocks/vrf.py
+++ b/gso/products/product_blocks/vrf.py
@@ -1,4 +1,4 @@
-"""Product blocks for :term:`VRF` Virtual Routing and Forwarding."""
+"""Product blocks for VRF Virtual Routing and Forwarding."""
 
 from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle
@@ -7,7 +7,7 @@ from gso.products.product_blocks.router import RouterBlock, RouterBlockInactive,
 
 
 class VRFBlockInactive(ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="VRFBlock"):
-    """An inactive :term:`VRF` subscription. See :class:`VRFBlock`."""
+    """An inactive VRF subscription. See `VRFBlock`."""
 
     vrf_router_list: list[RouterBlockInactive]
     vrf_name: str | None = None
@@ -17,7 +17,7 @@ class VRFBlockInactive(ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITI
 
 
 class VRFBlockProvisioning(VRFBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
-    """A provisioning :term:`VRF` subscription. See :class:`VRFBlock`."""
+    """A provisioning VRF subscription. See `VRFBlock`."""
 
     vrf_router_list: list[RouterBlockProvisioning]  # type: ignore[assignment]
     vrf_name: str
@@ -27,15 +27,18 @@ class VRFBlockProvisioning(VRFBlockInactive, lifecycle=[SubscriptionLifecycle.PR
 
 
 class VRFBlock(VRFBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """Represents an active :term:`VRF` subscription block."""
+    """Represents an active VRF subscription block.
+
+    Attributes:
+        vrf_router_list: List of VRF routers.
+        vrf_name: Unique name identifying this VRF.
+        route_distinguisher: Route Distinguisher ensuring unique route identification within this VRF.
+        route_target: Route Target defining routing policies for importing/exporting routes.
+        vrf_as_number: AS number of the VRF.
+    """
 
-    #: List of VRF routers
     vrf_router_list: list[RouterBlock]  # type: ignore[assignment]
-    #: Unique name identifying this VRF.
     vrf_name: str
-    #: Route Distinguisher (RD) ensuring unique route identification within this VRF.
     route_distinguisher: str
-    #: Route Target (RT) defining routing policies for importing/exporting routes.
     route_target: str
-    #: AS number of the VRF
     vrf_as_number: int | None = None
diff --git a/gso/products/product_types/vrf.py b/gso/products/product_types/vrf.py
index 348d6b922aed7d4be9936bd3749b9b7be280ee6d..653e461a3cbd7a23b93645ed5c9f7f6206e4fbc2 100644
--- a/gso/products/product_types/vrf.py
+++ b/gso/products/product_types/vrf.py
@@ -1,4 +1,4 @@
-"""A :term:`VRF` product type."""
+"""A VRF product type."""
 
 from orchestrator.domain.base import SubscriptionModel
 from orchestrator.types import SubscriptionLifecycle
@@ -7,18 +7,18 @@ from gso.products.product_blocks.vrf import VRFBlock, VRFBlockInactive, VRFBlock
 
 
 class VRFInactive(SubscriptionModel, is_base=True):
-    """An inactive :term:`VRF`."""
+    """An inactive VRF."""
 
     vrf: VRFBlockInactive
 
 
 class VRFProvisioning(VRFInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
-    """A provisioning :term:`VRF`."""
+    """A provisioning VRF."""
 
     vrf: VRFBlockProvisioning
 
 
 class VRF(VRFProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """A :term:`VRF` that is currently active."""
+    """A VRF that is currently active."""
 
     vrf: VRFBlock
diff --git a/gso/schedules/scheduling.py b/gso/schedules/scheduling.py
index 508af69ec81c7c53cbfd9abe09724b8eb154d837..1f99f582ffb7f80ad1f0d497a89fe4fa85f281fa 100644
--- a/gso/schedules/scheduling.py
+++ b/gso/schedules/scheduling.py
@@ -25,11 +25,11 @@ def scheduler(cron_scheduler_config: CronScheduleConfig) -> Callable[[Callable],
     """Schedule a Celery task using crontab-like timing.
 
     Examples:
-        - ``minute='*/15'``: Run every 15 minutes.
-        - ``hour='*/3'``: Run every 3 hours.
-        - ``day_of_week='mon-fri'``: Run on weekdays only.
-        - ``day_of_month='1-7,15-21'``: Run on the first and third weeks of the month.
-        - ``month_of_year='*/3'``: Run on the first month of each quarter.
+        - `minute='*/15'`: Run every 15 minutes.
+        - `hour='*/3'`: Run every 3 hours.
+        - `day_of_week='mon-fri'`: Run on weekdays only.
+        - `day_of_month='1-7,15-21'`: Run on the first and third weeks of the month.
+        - `month_of_year='*/3'`: Run on the first month of each quarter.
 
     All time units can be specified with lists of numbers or crontab pattern strings for advanced scheduling.
     All specified time parts (minute, hour, day, etc.) must align for a task to run.
diff --git a/gso/services/infoblox.py b/gso/services/infoblox.py
index cad3fb436961519534cf403db7daa386f8764bbb..49ca5d3ec25e78d3b12287d24e3b62bbbdaf1116 100644
--- a/gso/services/infoblox.py
+++ b/gso/services/infoblox.py
@@ -10,7 +10,14 @@ from infoblox_client.exceptions import (
 )
 
 from gso.settings import IPAMParams, load_oss_params
-from gso.utils.types.ip_address import IPv4AddressType, IPv6AddressType
+from gso.utils.types.ip_address import (
+    IPv4AddressType,
+    IPv4Netmask,
+    IPv4NetworkType,
+    IPv6AddressType,
+    IPv6Netmask,
+    IPv6NetworkType,
+)
 
 logger = getLogger(__name__)
 NULL_MAC = "00:00:00:00:00:00"
@@ -41,7 +48,7 @@ def _allocate_network(  # noqa: PLR0917
     conn: connector.Connector,
     dns_view: str,
     network_view: str,
-    netmask: int,
+    netmask: IPv4Netmask | IPv6Netmask,
     containers: list[str],
     comment: str | None = "",
 ) -> ipaddress.IPv4Network | ipaddress.IPv6Network:
@@ -73,14 +80,52 @@ def _allocate_network(  # noqa: PLR0917
     raise AllocationError(msg)
 
 
+def create_v4_network_by_ip(
+    dns_view: str, network_view: str, network: IPv4NetworkType, comment: str | None = ""
+) -> None:
+    """Register an IPv4 network at the given location.
+
+    Raises:
+        AllocationError: on failure.
+    """
+    conn, _ = _setup_connection()
+    created_net = objects.NetworkV4.create(
+        conn, network=network, view=dns_view, network_view=network_view, comment=comment
+    )
+    if created_net.response != "Infoblox Object created":
+        msg = f"Failed to allocate network at {network}. Response from Netbox: {created_net.response}"
+        raise AllocationError(msg)
+    msg = f"Successfully registered new network at {network}"
+    logger.debug(msg)
+
+
+def create_v6_network_by_ip(
+    dns_view: str, network_view: str, network: IPv6NetworkType, comment: str | None = ""
+) -> None:
+    """Register an IPv6 network at the given location.
+
+    Raises:
+        AllocationError: on failure.
+    """
+    conn, _ = _setup_connection()
+    created_net = objects.NetworkV6.create(
+        conn, network=network, view=dns_view, network_view=network_view, comment=comment
+    )
+    if created_net.response != "Infoblox Object created":
+        msg = f"Failed to allocate network at {network}. Response from Netbox: {created_net.response}"
+        raise AllocationError(msg)
+    msg = f"Successfully registered new network at {network}"
+    logger.debug(msg)
+
+
 def hostname_available(hostname: str) -> bool:
     """Check whether a hostname is still available **in Infoblox**.
 
     Check whether Infoblox already has a ``infoblox_client.objects.HostRecord`` that matches the given hostname.
 
-    .. warning::
-       This method only checks within the Infoblox instance, and not the rest of the internet. The hostname could
-       therefore still be taken elsewhere.
+    !!! danger
+        This method only checks within the Infoblox instance, and not the rest of the internet. The hostname could
+        therefore still be taken elsewhere.
 
     Args:
         hostname: The hostname to be checked.
@@ -229,38 +274,49 @@ def allocate_host(
 
 def create_host_by_ip(
     hostname: str,
-    ipv4_address: IPv4AddressType,
-    ipv6_address: IPv6AddressType,
     service_type: str,
     comment: str,
+    *,
+    ipv4_address: IPv4AddressType | None = None,
+    ipv6_address: IPv6AddressType | None = None,
 ) -> None:
     """Create a new host record with a given IPv4 and IPv6 address.
 
     Args:
         hostname: The FQDN of the new host.
-        ipv4_address: The IPv4 address of the new host.
-        ipv6_address: The IPv6 address of the new host.
         service_type: The relevant service type, used to deduce the correct ``dns_view`` and ``network_view`` in
             Infoblox.
         comment: The comment stored in this Infoblox record, most likely the relevant ``subscription_id`` in GSO.
+        ipv4_address: The IPv4 address of the new host.
+        ipv6_address: The IPv6 address of the new host.
     """
     if not hostname_available(hostname):
         msg = f"FQDN '{hostname}' is already in use, allocation aborted."
         raise AllocationError(msg)
+    if not (ipv4_address or ipv6_address):
+        msg = "At least one IP address must be given to create a record for. Received None."
+        raise AllocationError(msg)
 
     conn, oss = _setup_connection()
-    ipv6_object = objects.IP.create(ip=str(ipv6_address), mac=NULL_MAC, configure_for_dhcp=False)
-    ipv4_object = objects.IP.create(ip=str(ipv4_address), mac=NULL_MAC, configure_for_dhcp=False)
     dns_view = getattr(oss, service_type).dns_view
     network_view = getattr(oss, service_type).network_view
 
     # This needs to be done in two steps, otherwise only one of the IP addresses is stored.
-    objects.HostRecord.create(
-        conn, ip=ipv6_object, name=hostname, comment=comment, view=dns_view, network_view=network_view
-    )
-    new_host = find_host_by_fqdn(hostname)
-    new_host.ipv4addrs = [ipv4_object]
-    new_host.update()
+    if ipv6_address:  # We first register the IPv6 record.
+        ipv6_object = objects.IP.create(ip=str(ipv6_address), mac=NULL_MAC, configure_for_dhcp=False)
+        objects.HostRecord.create(
+            conn, ip=ipv6_object, name=hostname, comment=comment, view=dns_view, network_view=network_view
+        )
+        if ipv4_address:  # If set, we also register the IPv4 record.
+            new_host = find_host_by_fqdn(hostname)
+            ipv4_object = objects.IP.create(ip=str(ipv4_address), mac=NULL_MAC, configure_for_dhcp=False)
+            new_host.ipv4addrs = [ipv4_object]
+            new_host.update()
+    else:  # IPv6 is not set, it must therefore be IPv4 only.
+        ipv4_object = objects.IP.create(ip=str(ipv4_address), mac=NULL_MAC, configure_for_dhcp=False)
+        objects.HostRecord.create(
+            conn, ip=ipv4_object, name=hostname, comment=comment, view=dns_view, network_view=network_view
+        )
 
 
 def find_host_by_ip(ip_addr: ipaddress.IPv4Address | ipaddress.IPv6Address) -> objects.HostRecord | None:
@@ -290,11 +346,7 @@ def find_host_by_fqdn(fqdn: str) -> objects.HostRecord:
         fqdn: The FQDN of a host that is searched for.
     """
     conn, _ = _setup_connection()
-    return objects.HostRecord.search(
-        conn,
-        name=fqdn,
-        return_fields=["ipv4addrs", "name", "view", "aliases", "comment"],
-    )
+    return objects.HostRecord.search(conn, name=fqdn, return_fields=["ipv4addrs", "name", "view", "aliases", "comment"])
 
 
 def find_v6_host_by_fqdn(fqdn: str) -> objects.HostRecordV6:
diff --git a/gso/services/kentik_client.py b/gso/services/kentik_client.py
index cb9c30932c271489974d5facf795e09e362352e0..86e81a0c418d251e9c385b8273706388f1f9efc6 100644
--- a/gso/services/kentik_client.py
+++ b/gso/services/kentik_client.py
@@ -62,7 +62,7 @@ class KentikClient:
         """List all devices in Kentik.
 
         Returns:
-        a list of shape ``[{**device_1}, {**device_2}, ..., {**device_n}]}``.
+        a list of shape `[{**device_1}, {**device_2}, ..., {**device_n}]}`.
         """
         return self._send_request("GET", "v5/devices")["devices"]
 
@@ -101,10 +101,8 @@ class KentikClient:
 
         If the site is not found, return an empty dict.
 
-        .. vale off
-
         Args:
-            site_slug: The name of the site, should be a three-letter slug like COR or POZ.
+            site_slug: The name of the site, should be a three-letter slug like `COR` or `POZ`.
         """
         sites = self.get_sites()
         for site in sites:
@@ -116,40 +114,42 @@ class KentikClient:
     def get_plans(self) -> list[dict[str, Any]]:
         """Get all Kentik plans available.
 
+        Returns a list of `plan` objects that each have the following shape:
+
+        <!-- vale off -->
+        ```py
+        "plan": {
+            "active": true,
+            "bgp_enabled": true,
+            "cdate" "1970-01-01T01:01:01.000Z",
+            "company_id": 111111,
+            "description": "A description of this plan",
+            "deviceTypes": [
+                {"device_type": "router"},
+                {"device_type": "host-nprobe-dns-www"}
+            ],
+            "devices": [
+                {
+                    "id": "111111",
+                    "device_name": "rt0.city.tld.internal",
+                    "device_type": "router"
+                },
+            ],
+            "edate": "2999-01-01T09:09:09.000Z",
+            "fast_retention": 10,
+            "full_retention": 5,
+            "id": 11111,
+            "max_bigdata_fps": 100,
+            "max_devices": 9001,
+            "max_fps": 200,
+            "name": "KENTIK-PLAN-01",
+            "metadata": {},
+        }
+        ```
+        <!-- vale on -->
+
         Returns:
-            a list of ``plans`` that each have the following shape:
-
-            .. vale off
-            .. code-block:: json
-
-                "plan": {
-                    "active": true,
-                    "bgp_enabled": true,
-                    "cdate" "1970-01-01T01:01:01.000Z",
-                    "company_id": 111111,
-                    "description": "A description of this plan",
-                    "deviceTypes": [
-                        {"device_type": "router"},
-                        {"device_type": "host-nprobe-dns-www"}
-                    ],
-                    "devices": [
-                        {
-                            "id": "111111",
-                            "device_name": "rt0.city.tld.internal",
-                            "device_type": "router"
-                        },
-                    ],
-                    "edate": "2999-01-01T09:09:09.000Z",
-                    "fast_retention": 10,
-                    "full_retention": 5,
-                    "id": 11111,
-                    "max_bigdata_fps": 100,
-                    "max_devices": 9001,
-                    "max_fps": 200,
-                    "name": "KENTIK-PLAN-01",
-                    "metadata": {},
-                }
-            .. vale on
+            A list of plans configured in Kentik.
         """
         return self._send_request("GET", "v5/plans")["plans"]
 
diff --git a/gso/services/lso_client.py b/gso/services/lso_client.py
index 1529ca1fb520d78193e787eaf24ed0d0b3d1067d..7c6e49a28ca5d83e985037174aa0e31d0b4a5822 100644
--- a/gso/services/lso_client.py
+++ b/gso/services/lso_client.py
@@ -17,10 +17,12 @@ from orchestrator.workflow import Step, StepList, begin, callback_step, conditio
 from pydantic import ConfigDict
 from pydantic_forms.types import FormGenerator
 from pydantic_forms.validators import Label, LongText, ReadOnlyField
+from unidecode import unidecode
 
 from gso import settings
 
 logger = logging.getLogger(__name__)
+RUNNING_ANSIBLE_PLAYBOOK_STEP_NAME = "Running Ansible playbook"
 
 
 class _LSOState(TypedDict):  # noqa: PYI049
@@ -42,6 +44,9 @@ def _send_request(parameters: dict, callback_route: str) -> None:
         parameters: JSON body for the request, which will almost always at least consist of a subscription object, and a
             boolean value to indicate a dry run.
         callback_route: The callback route that should be used to resume the workflow.
+
+    Raises:
+        HTTPError: When receiving a non-successful status code from LSO.
     """
     oss = settings.load_oss_params()
     params = oss.PROVISIONING_PROXY
@@ -68,37 +73,37 @@ def _execute_playbook(
     For example, an inventory consisting of two hosts, which each a unique host variable assigned to them looks as
     follows:
 
-    .. vale off
-    .. code-block:: json
-
-        "inventory": {
-            "all": {
-                "hosts": {
-                    "host1.local": {
-                        "foo": "bar"
-                    },
-                    "host2.local": {
-                        "key": "value"
-                    },
-                    "host3.local": None
-                }
+    ```py
+    "inventory": {
+        "all": {
+            "hosts": {
+                "host1.local": {
+                    "foo": "bar"
+                },
+                "host2.local": {
+                    "key": "value"
+                },
+                "host3.local": None
             }
         }
-    .. vale on
+    }
+    ```
 
-    .. warning::
-       Note the fact that the collection of all hosts is a dictionary, and not a list of strings. Ansible expects each
-       host to be a key-value pair. The key is the FQDN of a host, and the value always ``null``.
+    !!! danger
+        Note the fact that the collection of all hosts is a dictionary, and not a list of strings. Ansible expects each
+        host to be a key-value pair. The key is the FQDN of a host, and the value always `null`.
 
     The extra vars can be a simple dict consisting of key-value pairs, for example:
 
-    .. code-block:: json
-
-        "extra_vars": {
-            "dry_run": true,
-            "commit_comment": "I am a robot!",
-            "verb": "deploy"
-        }
+    <!-- vale off -->
+    ```py
+    "extra_vars": {
+        "dry_run": true,
+        "commit_comment": "I am a robot!",
+        "verb": "deploy"
+    }
+    ```
+    <!-- vale on -->
 
     Args:
         playbook_name: Filename of the playbook that is to be executed. It must be present on the remote system running
@@ -106,12 +111,13 @@ def _execute_playbook(
         callback_route: The endpoint at which GSO expects a callback to continue the workflow executing this step.
         inventory: An inventory of machines at which the playbook is targeted. Must be in YAML-compatible format.
         extra_vars: Any extra variables that the playbook relies on. This can include a subscription object, a boolean
-            value indicating a dry run, a commit comment, etc.
+            value indicating a dry run, a commit comment, etc. All unicode character values are decoded to prevent
+            sending special characters to remote machines that don't support this.
     """
     parameters = {
         "playbook_name": playbook_name,
         "inventory": inventory,
-        "extra_vars": extra_vars,
+        "extra_vars": json.loads(unidecode(json.dumps(extra_vars, ensure_ascii=False))),
     }
 
     _send_request(parameters, callback_route)
@@ -163,7 +169,7 @@ def _clean_state() -> State:
     }
 
 
-def _inventory_is_set(state: State) -> bool:
+def validate_inventory_is_set(state: State) -> bool:
     """Validate whether the passed Ansible inventory is empty.
 
     If the inventory is empty, which can happen in select cases, there should be no playbook run. This conditional will
@@ -180,7 +186,7 @@ def _inventory_is_set(state: State) -> bool:
     return state["inventory"]["all"]["hosts"]
 
 
-_inventory_is_not_empty = conditional(_inventory_is_set)
+_inventory_is_not_empty = conditional(validate_inventory_is_set)
 
 
 def lso_interaction(provisioning_step: Step) -> StepList:
@@ -190,7 +196,7 @@ def lso_interaction(provisioning_step: Step) -> StepList:
     to provision service subscriptions. If the playbook fails, this step will also fail, allowing for the user to retry
     provisioning from the UI.
 
-    Optionally, the keys ``lso_result_title`` and ``lso_result_extra_label`` can be added to the state before running
+    Optionally, the keys `lso_result_title` and `lso_result_extra_label` can be added to the state before running
     this interaction. They will be used to customise the input step that shows the outcome of the LSO
     interaction.
 
@@ -206,7 +212,7 @@ def lso_interaction(provisioning_step: Step) -> StepList:
         >> _inventory_is_not_empty(
             begin
             >> callback_step(
-                name="Running Ansible playbook", action_step=_execute_playbook, validate_step=_evaluate_results
+                name=RUNNING_ANSIBLE_PLAYBOOK_STEP_NAME, action_step=_execute_playbook, validate_step=_evaluate_results
             )
             >> step("Inject result title")(lambda: {"lso_result_title": provisioning_step.name})
             >> _show_results
@@ -218,13 +224,13 @@ def lso_interaction(provisioning_step: Step) -> StepList:
 def indifferent_lso_interaction(provisioning_step: Step) -> StepList:
     """Interact with the provisioning proxy LSO using a callback step.
 
-    This interaction is identical from the one described in ``lso_interaction()``, with one functional difference.
-    Whereas the ``lso_interaction()`` will make the workflow step fail on unsuccessful interaction, this step will not.
+    This interaction is identical from the one described in `lso_interaction()`, with one functional difference.
+    Whereas the `lso_interaction()` will make the workflow step fail on unsuccessful interaction, this step will not.
     It is therefore indifferent about the outcome of the Ansible playbook that is executed.
 
-    .. warning::
-       Using this interaction requires the operator to carefully evaluate the outcome of a playbook themselves. If a
-       playbook fails, this will not cause the workflow to fail.
+    !!! danger
+        Using this interaction requires the operator to manually evaluate the outcome of a playbook. If a playbook
+        fails, this will not cause the workflow to fail.
 
     Args:
         provisioning_step: A workflow step that performs an operation remotely using the provisioning proxy.
@@ -238,7 +244,7 @@ def indifferent_lso_interaction(provisioning_step: Step) -> StepList:
         >> _inventory_is_not_empty(
             begin
             >> callback_step(
-                name="Running Ansible playbook", action_step=_execute_playbook, validate_step=_ignore_results
+                name=RUNNING_ANSIBLE_PLAYBOOK_STEP_NAME, action_step=_execute_playbook, validate_step=_ignore_results
             )
             >> step("Inject result title")(lambda: {"lso_result_title": provisioning_step.name})
             >> _show_results
@@ -252,7 +258,7 @@ def anonymous_lso_interaction(provisioning_step: Step, validation_step: Step = _
 
     Similar to the indifferent LSO interaction, there also is the anonymous interaction. Output is not ignored
     but no input step is created to display the results.
-    A custom validation step may be given as input. This validation step should look inside the ``callback_result`` key
+    A custom validation step may be given as input. This validation step should look inside the `callback_result` key
     in the current state. By default, only the return code of the playbook execution is evaluated.
 
     Args:
@@ -263,7 +269,9 @@ def anonymous_lso_interaction(provisioning_step: Step, validation_step: Step = _
         begin
         >> provisioning_step
         >> _inventory_is_not_empty(
-            callback_step(name="Running Ansible playbook", action_step=_execute_playbook, validate_step=validation_step)
+            callback_step(
+                name=RUNNING_ANSIBLE_PLAYBOOK_STEP_NAME, action_step=_execute_playbook, validate_step=validation_step
+            )
         )
         >> _clean_state
     )
@@ -282,7 +290,7 @@ def anonymous_indifferent_lso_interaction(provisioning_step: Step) -> StepList:
         >> provisioning_step
         >> _inventory_is_not_empty(
             callback_step(
-                name="Running Ansible playbook",
+                name=RUNNING_ANSIBLE_PLAYBOOK_STEP_NAME,
                 action_step=_execute_playbook,
                 validate_step=_ignore_results,
             )
diff --git a/gso/services/netbox_client.py b/gso/services/netbox_client.py
index 4b6560b9cf78fb07ffde0e5b2604620c35844990..2928263c920da52e0993fbb43ddc4dbfa2d20283 100644
--- a/gso/services/netbox_client.py
+++ b/gso/services/netbox_client.py
@@ -319,7 +319,7 @@ class NetboxClient:
 
     @staticmethod
     def calculate_speed_bits_per_sec(speed: str) -> int:
-        """Extract the numeric part from the speed."""
+        """Calculate the numeric part from the speed."""
         numeric_part = int("".join(filter(str.isdigit, speed)))
         # Convert to bits per second
         return numeric_part * 1000000
diff --git a/gso/services/subscriptions.py b/gso/services/subscriptions.py
index 9185265c09d31a4c6a010336123a407a969404b1..c42b4a71fa2c0af63e0307ba791e2cba5db32dde 100644
--- a/gso/services/subscriptions.py
+++ b/gso/services/subscriptions.py
@@ -19,6 +19,8 @@ from orchestrator.db import (
 from orchestrator.domain import SubscriptionModel
 from orchestrator.services.subscriptions import query_in_use_by_subscriptions
 from orchestrator.types import SubscriptionLifecycle, UUIDstr
+from sqlalchemy import text
+from sqlalchemy.exc import SQLAlchemyError
 
 from gso.products import ProductName, ProductType
 from gso.products.product_types.site import Site
@@ -43,7 +45,7 @@ def get_subscriptions(
         partner_id: The customer id of subscriptions.
 
     Returns:
-        A list of ``SubscriptionType`` objects that match the query.
+        A list of `SubscriptionType` objects that match the query.
     """
     if not includes:
         includes = [col.name for col in SubscriptionTable.__table__.columns]
@@ -159,9 +161,9 @@ def get_non_terminated_iptrunk_subscriptions(includes: list[str] | None = None)
 def get_trunks_that_terminate_on_router(
     subscription_id: UUIDstr, lifecycle_state: SubscriptionLifecycle
 ) -> list[SubscriptionTable]:
-    """Get all IP trunk subscriptions that terminate on the given ``subscription_id`` of a Router.
+    """Get all IP trunk subscriptions that terminate on the given `subscription_id` of a Router.
 
-    Given a ``subscription_id`` of a Router subscription, this method gives a list of all IP trunk subscriptions that
+    Given a `subscription_id` of a Router subscription, this method gives a list of all IP trunk subscriptions that
     terminate on this Router. The given lifecycle state dictates the state of trunk subscriptions that are counted as
     terminating on this router.
 
@@ -329,3 +331,39 @@ def is_virtual_circuit_id_available(virtual_circuit_id: str) -> bool:
         True if the virtual circuit ID is unique (not found), False if it exists.
     """
     return is_resource_type_value_unique("virtual_circuit_id", virtual_circuit_id)
+
+
+def generate_unique_gs_id() -> str:
+    """Generate a unique GS ID using the gs_id_seq database sequence.
+
+    Returns:
+        str: A unique GS ID in the format `GS-<number>`.
+
+    Raises:
+        ValueError: If there is an error generating the ID.
+    """
+    try:
+        new_id = db.session.execute(text("SELECT nextval('gs_id_seq')")).scalar_one()
+    except SQLAlchemyError as exc:
+        error_message = f"Error generating GS ID: {exc}"
+        raise ValueError(error_message) from exc
+    else:
+        return f"GS-{new_id}"
+
+
+def generate_unique_ga_id() -> str:
+    """Generate a unique GA ID using the ga_id_seq database sequence.
+
+    Returns:
+        str: A unique GA ID in the format `GA-<number>`.
+
+    Raises:
+        ValueError: If there is an error generating the ID.
+    """
+    try:
+        new_id = db.session.execute(text("SELECT nextval('ga_id_seq')")).scalar_one()
+    except SQLAlchemyError as exc:
+        error_message = f"Error generating GA ID: {exc}"
+        raise ValueError(error_message) from exc
+    else:
+        return f"GA-{new_id}"
diff --git a/gso/settings.py b/gso/settings.py
index 7f71a5605d52ec1e0df7184dce7880fe00f85f5f..d65ead42c5c43e1aacac30f3c44616194fc819ad 100644
--- a/gso/settings.py
+++ b/gso/settings.py
@@ -15,7 +15,7 @@ from pydantic import EmailStr
 from pydantic_forms.types import strEnum
 from pydantic_settings import BaseSettings
 
-from gso.utils.types.ip_address import IPV4Netmask, IPV6Netmask, PortNumber
+from gso.utils.types.ip_address import IPv4Netmask, IPv6Netmask, PortNumber
 
 logger = logging.getLogger(__name__)
 
@@ -65,7 +65,7 @@ class V4NetworkParams(BaseSettings):
 
     containers: list[ipaddress.IPv4Network]
     networks: list[ipaddress.IPv4Network]
-    mask: IPV4Netmask
+    mask: IPv4Netmask
 
 
 class V6NetworkParams(BaseSettings):
@@ -73,7 +73,7 @@ class V6NetworkParams(BaseSettings):
 
     containers: list[ipaddress.IPv6Network]
     networks: list[ipaddress.IPv6Network]
-    mask: IPV6Netmask
+    mask: IPv6Netmask
 
 
 class ServiceNetworkParams(BaseSettings):
@@ -98,6 +98,7 @@ class IPAMParams(BaseSettings):
     GEANT_IP: ServiceNetworkParams
     SI: ServiceNetworkParams
     LT_IAS: ServiceNetworkParams
+    LAN_SWITCH_INTERCONNECT: ServiceNetworkParams
 
 
 class MonitoringSNMPV2Params(BaseSettings):
@@ -128,9 +129,12 @@ class SNMPParams(BaseSettings):
     """Parameters for SNMP in LibreNMS."""
 
     v2c: MonitoringSNMPV2Params
-    #: .. versionadded :: 2.0
-    #:    Support for SNMP v3 will get added in a later version of GSO. Parameters are optional for now.
     v3: MonitoringSNMPV3Params | None = None
+    """
+    !!! example "Optional parameter"
+
+        Support for SNMP v3 will get added in a later version of GSO. Parameters are optional for now.
+    """
 
 
 class MonitoringParams(BaseSettings):
@@ -156,7 +160,12 @@ class NetBoxParams(BaseSettings):
 
 
 class EmailParams(BaseSettings):
-    """Parameters for the email service."""
+    """Parameters for the email service.
+
+    Attributes:
+        notification_email_destinations: List of email addresses that should receive notifications when validation of a
+            subscription fails. Can be a comma-separated list of multiple addresses.
+    """
 
     from_address: EmailStr
     smtp_host: str
@@ -164,8 +173,6 @@ class EmailParams(BaseSettings):
     starttls_enabled: bool
     smtp_username: str | None = None
     smtp_password: str | None = None
-    #: List of email addresses that should receive notifications when validation of a subscription fails.
-    #: Can be a comma-separated list of multiple addresses.
     notification_email_destinations: str
 
 
diff --git a/gso/translations/en-GB.json b/gso/translations/en-GB.json
index f1f66f075835a00fbe3ca5a5cdffa2c515388d0d..c62d1c4d0601eeee8a1c4e0553f7685f2f7b4fbd 100644
--- a/gso/translations/en-GB.json
+++ b/gso/translations/en-GB.json
@@ -12,10 +12,10 @@
             "site_tier": "Site Tier",
             "site_ts_address": "Site Terminal Server Address",
 
-            "hostname": "Hostname of the new router, only the part that comes before the first period",
+            "hostname": "Hostname of the new device, only the part that comes before the first period",
             "ts_address": "IP address of the terminal server",
             "ts_port": "Port number of the terminal server",
-            "vendor": "Router vendor",
+            "vendor": "Device vendor",
             "router_role": "Router role",
 
             "geant_s_sid": "GÉANT S-SID",
@@ -40,7 +40,7 @@
 
             "enable_lacp": "Enable LACP",
             "mac_address": "MAC address",
-            "geant_ga_id": "GÉANT GA-ID"
+            "ga_id": "GÉANT GA-ID"
         }
     },
     "workflow": {
diff --git a/gso/utils/helpers.py b/gso/utils/helpers.py
index 852e152f21f00605e55b2283ad1ca06c6f37e8c3..20d66cab639db87252278ad1cc70310740c65812 100644
--- a/gso/utils/helpers.py
+++ b/gso/utils/helpers.py
@@ -2,6 +2,7 @@
 
 import random
 import re
+from ipaddress import IPv4Network, IPv6Network
 from typing import TYPE_CHECKING
 from uuid import UUID
 
@@ -18,7 +19,7 @@ from gso.services.partners import get_all_partners
 from gso.services.subscriptions import is_virtual_circuit_id_available
 from gso.utils.shared_enums import Vendor
 from gso.utils.types.interfaces import PhysicalPortCapacity
-from gso.utils.types.ip_address import IPv4AddressType
+from gso.utils.types.ip_address import IPv4AddressType, IPv4NetworkType, IPv6NetworkType
 from gso.utils.types.virtual_identifiers import VC_ID
 
 if TYPE_CHECKING:
@@ -73,7 +74,7 @@ def available_lags_choices(router_id: UUID) -> Choice | None:
     """Return a list of available lags for a given router.
 
     For Nokia routers, return a list of available lags.
-    For Juniper routers, return ``None``.
+    For Juniper routers, return `None`.
     """
     if get_router_vendor(router_id) != Vendor.NOKIA:
         return None
@@ -85,7 +86,7 @@ def available_service_lags_choices(router_id: UUID) -> Choice | None:
     """Return a list of available lags for a given router for services.
 
     For Nokia routers, return a list of available lags.
-    For Juniper routers, return ``None``.
+    For Juniper routers, return `None`.
     """
     if get_router_vendor(router_id) != Vendor.NOKIA:
         return None
@@ -126,6 +127,28 @@ def generate_fqdn(hostname: str, site_name: str, country_code: str) -> str:
     return f"{hostname}.{site_name.lower()}.{country_code.lower()}{oss.IPAM.LO.domain_name}"
 
 
+def generate_lan_switch_interconnect_subnet_v4(site_internal_id: int) -> IPv4NetworkType:
+    """Generate an IPv4 network in which a LAN Switch Interconnect resides, given a Site internal ID."""
+    ipam_oss = settings.load_oss_params().IPAM.LAN_SWITCH_INTERCONNECT
+
+    result = str(ipam_oss.V4.containers[0]).split(".")[:2]  # Take the first two octets from the IPv4 network.
+    result.append(str(site_internal_id))  # Append the side ID as the third octet.
+    result.append(f"0/{ipam_oss.V4.mask}")  # Append the fourth octet, together with the netmask.
+
+    return IPv4Network(".".join(result))
+
+
+def generate_lan_switch_interconnect_subnet_v6(site_internal_id: int) -> IPv6NetworkType:
+    """Generate an IPv6 network in which a LAN Switch Interconnect resides, given a Site internal ID."""
+    ipam_oss = settings.load_oss_params().IPAM.LAN_SWITCH_INTERCONNECT
+
+    result = IPv6Network(ipam_oss.V6.containers[0]).exploded[:17]  # Take the first 56 bits of the network
+    result += str(hex(site_internal_id)[2:])  # Append the site internal id for bytes 57 to 64 as hexadecimal number
+    result += f"::/{ipam_oss.V6.mask}"  # And fill the rest of the network with empty bits
+
+    return IPv6Network(result)
+
+
 def generate_inventory_for_routers(
     router_role: RouterRole,
     exclude_routers: list[str] | None = None,
@@ -141,7 +164,7 @@ def generate_inventory_for_routers(
         router_role: The role of the routers to include in the inventory.
         exclude_routers: List of routers to exclude from the inventory.
         router_vendor: The vendor of the routers to include in the inventory.
-        include_provisioning_routers: Include routers that are in a ``PROVISIONING`` state.
+        include_provisioning_routers: Include routers that are in a `PROVISIONING` state.
 
     Returns:
         A dictionary representing the inventory of active routers.
@@ -239,7 +262,7 @@ def active_edge_port_selector(*, partner_id: UUIDstr | None = None) -> Choice:
     )
 
     if partner_id:
-        # ``partner_id`` is set, so we will filter accordingly.
+        # `partner_id` is set, so we will filter accordingly.
         edge_port_subscriptions = list(
             filter(lambda subscription: bool(subscription["customer_id"] == partner_id), edge_port_subscriptions)
         )
diff --git a/gso/utils/types/base_site.py b/gso/utils/types/base_site.py
index c5af009ad460e56619752688f52dc2bdfa5f9e46..753d0ab323fc9289192e260d9b88680acb139bdb 100644
--- a/gso/utils/types/base_site.py
+++ b/gso/utils/types/base_site.py
@@ -24,3 +24,4 @@ class BaseSiteValidatorModel(BaseModel):
     site_latitude: LatitudeCoordinate
     site_longitude: LongitudeCoordinate
     partner: str
+    site_contains_optical_equipment: bool = True
diff --git a/gso/utils/types/geant_ids.py b/gso/utils/types/geant_ids.py
new file mode 100644
index 0000000000000000000000000000000000000000..72afc7f4113035b28fc8df8d2fe812145e0e7238
--- /dev/null
+++ b/gso/utils/types/geant_ids.py
@@ -0,0 +1,49 @@
+"""Type definitions for the GA and GS IDs."""
+
+from functools import partial
+from typing import Annotated, Literal
+
+from pydantic import AfterValidator
+
+from gso.services.subscriptions import is_resource_type_value_unique
+
+
+def validate_id(value: str, prefix: Literal["GA", "GS"], field_name: str) -> str:
+    """Validate that the ID is unique, has the correct prefix, and is within valid constraints.
+
+    Args:
+        value: The ID value to validate.
+        prefix: The required prefix for the ID.
+        field_name: The database field name to check for uniqueness.
+
+    Raises:
+        ValueError: If the ID is not valid.
+
+    Returns:
+        str: The validated ID.
+    """
+    min_range = 50000
+    max_range = 99999
+    if not value.startswith(prefix):
+        err = f"{field_name} must start with the prefix '{prefix}'."
+        raise ValueError(err)
+
+    try:
+        numeric_part = int(value.split("-")[-1])
+    except ValueError:
+        err = f"{field_name} must have a numeric part after the prefix '{prefix}'."
+        raise ValueError(err) from ValueError
+
+    if min_range <= numeric_part <= max_range:
+        err = f"{field_name} must not have a numeric part between {min_range} and {max_range}, received {numeric_part}"
+        raise ValueError(err)
+
+    if not is_resource_type_value_unique(field_name, value):
+        err = f"{field_name} must be unique, {value} is already in use."
+        raise ValueError(err)
+
+    return value
+
+
+IMPORTED_GA_ID = Annotated[str, AfterValidator(partial(validate_id, prefix="GA", field_name="ga_id"))]
+IMPORTED_GS_ID = Annotated[str, AfterValidator(partial(validate_id, prefix="GS", field_name="gs_id"))]
diff --git a/gso/utils/types/interfaces.py b/gso/utils/types/interfaces.py
index 80ac4a5f84a804ed1ea97f8d8fade083ce5743a7..16f69f3aebcd75be9db7cbf9dde59114c6d86fce 100644
--- a/gso/utils/types/interfaces.py
+++ b/gso/utils/types/interfaces.py
@@ -25,7 +25,7 @@ class LAGMember(BaseModel):
 def validate_interface_names_are_unique(interfaces: list[LAGMember]) -> list[LAGMember]:
     """Verify if interfaces are unique.
 
-    Raises a ``ValueError`` if the interfaces are not unique.
+    Raises a `ValueError` if the interfaces are not unique.
 
     Args:
         interfaces: The list of interfaces.
@@ -54,10 +54,10 @@ def validate_juniper_phy_interface_name(interface_name: str) -> str:
     Returns:
         The interface name if match was successful, otherwise it will throw a ValueError exception.
     """
-    pattern = re.compile(r"^(ge|et|xe)-1?[0-9]/[0-9]{1,2}/[0-9]{1,2}$")
+    pattern = re.compile(r"^(ge|et|xe)-1?\d/\d{1,2}/\d{1,2}$")
     if not bool(pattern.match(interface_name)):
         error_msg = (
-            f"Invalid interface name. The interface name should be of format: xe-1/0/0. " f"Got: {interface_name}"
+            f"Invalid interface name. The interface name should be of format: xe-1/0/0, received: {interface_name}"
         )
         raise ValueError(error_msg)
     return interface_name
@@ -113,7 +113,7 @@ def bandwidth_string_is_valid(bandwidth_string: str) -> str:
     if len(bandwidth_string) < 2:  # noqa: PLR2004 not a magic value
         raise ValueError(msg)
 
-    if bandwidth_string[-1:] not in "K" "M" "G" "T":
+    if bandwidth_string[-1:] not in set("K" "M" "G" "T"):
         raise ValueError(msg)
 
     try:
diff --git a/gso/utils/types/ip_address.py b/gso/utils/types/ip_address.py
index 3b91167fe978a863a133abe75c68b275a1b028fb..83710c3ce4bd07d8591fe95eb9a5e373513d66dc 100644
--- a/gso/utils/types/ip_address.py
+++ b/gso/utils/types/ip_address.py
@@ -4,7 +4,6 @@ import ipaddress
 from typing import Annotated, Any
 
 from pydantic import AfterValidator, Field, PlainSerializer
-from pydantic_forms.types import strEnum
 from typing_extensions import Doc
 
 
@@ -40,8 +39,8 @@ IPv6AddressType = Annotated[ipaddress.IPv6Address, PlainSerializer(_str, return_
 IPv6NetworkType = Annotated[ipaddress.IPv6Network, PlainSerializer(_str, return_type=str, when_used="always")]
 IPAddress = Annotated[str, AfterValidator(validate_ipv4_or_ipv6)]
 IPNetwork = Annotated[str, AfterValidator(validate_ipv4_or_ipv6_network)]
-IPV4Netmask = Annotated[int, Field(ge=0, le=32), Doc("A valid netmask for an IPv4 network or address.")]
-IPV6Netmask = Annotated[int, Field(ge=0, le=128), Doc("A valid netmask for an IPv6 network or address.")]
+IPv4Netmask = Annotated[int, Field(ge=0, le=32), Doc("A valid netmask for an IPv4 network or address.")]
+IPv6Netmask = Annotated[int, Field(ge=0, le=128), Doc("A valid netmask for an IPv6 network or address.")]
 PortNumber = Annotated[
     int,
     Field(
@@ -53,10 +52,3 @@ PortNumber = Annotated[
         "and can therefore not be selected for permanent allocation."
     ),
 ]
-
-
-class AddressSpace(strEnum):
-    """Types of address space. Can be private or public."""
-
-    PRIVATE = "PRIVATE"
-    PUBLIC = "PUBLIC"
diff --git a/gso/utils/types/site_name.py b/gso/utils/types/site_name.py
index 903b3468abbe744280a77c27bb2cf7861e8a389e..48d91dba779722c3caede4205c707cc210c3ce77 100644
--- a/gso/utils/types/site_name.py
+++ b/gso/utils/types/site_name.py
@@ -11,7 +11,7 @@ def validate_site_name(site_name: str) -> str:
 
     The site name must consist of three uppercase letters, optionally followed by a single digit.
     """
-    pattern = re.compile(r"^[A-Z]{3}[0-9]?$")
+    pattern = re.compile(r"^[A-Z]{3}\d?$")
     if not pattern.match(site_name):
         msg = (
             "Enter a valid site name. It must consist of three uppercase letters (A-Z), followed by an optional single "
diff --git a/gso/utils/types/virtual_identifiers.py b/gso/utils/types/virtual_identifiers.py
index 093678e38ff37bee29a3199514b63214d57f6b3b..78e5546e3a983268f3d9a5cd379b88c52fd07c77 100644
--- a/gso/utils/types/virtual_identifiers.py
+++ b/gso/utils/types/virtual_identifiers.py
@@ -13,3 +13,6 @@ VC_ID = Annotated[
         "A Virtual Circuit ID, the upper limit comes from the highest number that a service ID could be in Nokia srOS."
     ),
 ]
+
+DEFAULT_SWITCH_MANAGEMENT_VLAN_ID: VLAN_ID = 998
+DEFAULT_DCN_MANAGEMENT_VLAN_ID: VLAN_ID = 103
diff --git a/gso/utils/workflow_steps.py b/gso/utils/workflow_steps.py
index 721e18452e7f1aaded81a863bdfca514d8a11acd..0250eb8ce83649c8afe8a5d3cf287775eddce705 100644
--- a/gso/utils/workflow_steps.py
+++ b/gso/utils/workflow_steps.py
@@ -318,7 +318,7 @@ def set_isis_to_max(subscription: Iptrunk, process_id: UUIDstr, tt_number: str)
         "verb": "deploy",
         "config_object": "isis_interface",
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Deploy config for "
-        f"{subscription.iptrunk.geant_s_sid}",
+        f"{subscription.iptrunk.gs_id}",
     }
 
     return {
diff --git a/gso/workflows/edge_port/create_edge_port.py b/gso/workflows/edge_port/create_edge_port.py
index 2005d7c2601ec59b85c2ff05695f833695288e33..f715e10470ede68c7993f7af58c1344c20ffe3b4 100644
--- a/gso/workflows/edge_port/create_edge_port.py
+++ b/gso/workflows/edge_port/create_edge_port.py
@@ -22,6 +22,7 @@ from gso.products.product_types.router import Router
 from gso.services.lso_client import LSOState, lso_interaction
 from gso.services.netbox_client import NetboxClient
 from gso.services.partners import get_partner_by_id
+from gso.services.subscriptions import generate_unique_ga_id
 from gso.utils.helpers import (
     active_pe_router_selector,
     available_interfaces_choices,
@@ -52,7 +53,6 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
         minimum_links: int
         mac_address: str | None = None
         ignore_if_down: bool = False
-        geant_ga_id: str | None = None
 
         @model_validator(mode="after")
         def validate_number_of_members(self) -> Self:
@@ -104,7 +104,6 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
         "minimum_links",
         "mac_address",
         "ignore_if_down",
-        "geant_ga_id",
         "enable_lacp",
         "edge_port_name",
         "edge_port_description",
@@ -134,7 +133,6 @@ def initialize_subscription(
     encapsulation: EncapsulationType,
     name: str,
     minimum_links: int,
-    geant_ga_id: str | None,
     mac_address: str | None,
     partner: str,
     enable_lacp: bool,  # noqa: FBT001
@@ -152,10 +150,11 @@ def initialize_subscription(
     subscription.edge_port.edge_port_name = name
     subscription.edge_port.minimum_links = minimum_links
     subscription.edge_port.ignore_if_down = ignore_if_down
-    subscription.edge_port.geant_ga_id = geant_ga_id
+    ga_id = generate_unique_ga_id()
+    subscription.edge_port.ga_id = ga_id
     subscription.edge_port.mac_address = mac_address
     partner_name = get_partner_by_id(partner).name
-    subscription.description = f"Edge Port {name} on {router.router_fqdn}, " f"{partner_name}, {geant_ga_id or ""}"
+    subscription.description = f"Edge Port {name} on {router.router_fqdn}, {partner_name}, {ga_id or ""}"
     subscription.edge_port.edge_port_description = description
     for member in ae_members:
         subscription.edge_port.edge_port_ae_members.append(
diff --git a/gso/workflows/edge_port/create_imported_edge_port.py b/gso/workflows/edge_port/create_imported_edge_port.py
index 867965c741f2f96b7ae13df2b9b952b45f0056a0..cd5f7a7b164977d188272cb94bbdba366f66982e 100644
--- a/gso/workflows/edge_port/create_imported_edge_port.py
+++ b/gso/workflows/edge_port/create_imported_edge_port.py
@@ -20,6 +20,7 @@ from gso.products.product_types.router import Router
 from gso.services.partners import get_partner_by_name
 from gso.services.subscriptions import get_product_id_by_name
 from gso.utils.helpers import active_pe_router_selector
+from gso.utils.types.geant_ids import IMPORTED_GA_ID
 from gso.utils.types.interfaces import LAGMember, PhysicalPortCapacity
 
 
@@ -48,7 +49,7 @@ def initial_input_form_generator() -> FormGenerator:
         minimum_links: int
         mac_address: str | None = None
         ignore_if_down: bool = False
-        geant_ga_id: str | None = None
+        ga_id: IMPORTED_GA_ID | None = None
         description: str | None = None
         name: str
         ae_members: Annotated[list[LAGMember], AfterValidator(validate_unique_list)]
@@ -67,7 +68,7 @@ def initialize_subscription(
     encapsulation: EncapsulationType,
     name: str,
     minimum_links: int,
-    geant_ga_id: str | None,
+    ga_id: IMPORTED_GA_ID | None,
     mac_address: str | None,
     partner: str,
     enable_lacp: bool,  # noqa: FBT001
@@ -87,8 +88,8 @@ def initialize_subscription(
     subscription.edge_port.minimum_links = minimum_links
     subscription.edge_port.edge_port_type = service_type
     subscription.edge_port.ignore_if_down = ignore_if_down
-    subscription.edge_port.geant_ga_id = geant_ga_id
-    subscription.description = f"Edge Port {name} on {router.router_fqdn}, {partner}, {geant_ga_id or ""}"
+    subscription.edge_port.ga_id = ga_id
+    subscription.description = f"Edge Port {name} on {router.router_fqdn}, {partner}, {ga_id or ""}"
     for member in ae_members:
         subscription.edge_port.edge_port_ae_members.append(
             EdgePortAEMemberBlockInactive.new(subscription_id=uuid4(), **member)
diff --git a/gso/workflows/edge_port/modify_edge_port.py b/gso/workflows/edge_port/modify_edge_port.py
index bce56c745475e4a6e2cd43adb7dc3da77be9cbf5..b176eafcc9450dbbd7d8da0608bd0d13891c3f91 100644
--- a/gso/workflows/edge_port/modify_edge_port.py
+++ b/gso/workflows/edge_port/modify_edge_port.py
@@ -1,5 +1,6 @@
 """Modify an existing edge port subscription."""
 
+from functools import partial
 from typing import Annotated, Any, Self
 from uuid import uuid4
 
@@ -10,7 +11,7 @@ from orchestrator.targets import Target
 from orchestrator.workflow import StepList, begin, conditional, done, step
 from orchestrator.workflows.steps import resync, store_process_subscription, unsync
 from orchestrator.workflows.utils import wrap_modify_initial_input_form
-from pydantic import AfterValidator, ConfigDict, model_validator
+from pydantic import AfterValidator, ConfigDict, Field, model_validator
 from pydantic_forms.types import FormGenerator, State, UUIDstr
 from pydantic_forms.validators import ReadOnlyField, validate_unique_list
 
@@ -26,6 +27,7 @@ from gso.utils.helpers import (
 )
 from gso.utils.types.interfaces import LAGMember, PhysicalPortCapacity
 from gso.utils.types.tt_number import TTNumber
+from gso.utils.types.unique_field import validate_field_is_unique
 
 
 def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
@@ -43,7 +45,12 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
         minimum_links: int | None = subscription.edge_port.minimum_links or None
         mac_address: str | None = subscription.edge_port.mac_address or None
         ignore_if_down: bool = subscription.edge_port.ignore_if_down
-        geant_ga_id: str | None = subscription.edge_port.geant_ga_id or None
+        ga_id: (
+            Annotated[
+                str, AfterValidator(partial(validate_field_is_unique, subscription_id)), Field(pattern=r"^GA-\d{5}$")
+            ]
+            | None
+        ) = subscription.edge_port.ga_id or None
 
         @model_validator(mode="after")
         def validate_number_of_members(self) -> Self:
@@ -117,7 +124,7 @@ def modify_edge_port_subscription(
     encapsulation: EncapsulationType,
     minimum_links: int,
     mac_address: str | None,
-    geant_ga_id: str | None,
+    ga_id: str | None,
     enable_lacp: bool,  # noqa: FBT001
     ae_members: list[dict[str, str]],
     ignore_if_down: bool,  # noqa: FBT001
@@ -138,12 +145,12 @@ def modify_edge_port_subscription(
     subscription.edge_port.minimum_links = minimum_links
     subscription.edge_port.mac_address = mac_address
     subscription.edge_port.ignore_if_down = ignore_if_down
-    subscription.edge_port.geant_ga_id = geant_ga_id
+    subscription.edge_port.ga_id = ga_id
     subscription.edge_port.edge_port_description = description
     subscription.description = (
         f"Edge Port {subscription.edge_port.edge_port_name} on"
         f" {subscription.edge_port.node.router_fqdn},"
-        f" {get_partner_by_id(subscription.customer_id).name}, {geant_ga_id or ""}"
+        f" {get_partner_by_id(subscription.customer_id).name}, {ga_id or ""}"
     )
     subscription.edge_port.edge_port_ae_members.clear()
     partner_name = get_partner_by_id(subscription.customer_id).name
diff --git a/gso/workflows/edge_port/validate_edge_port.py b/gso/workflows/edge_port/validate_edge_port.py
index af82afee1bb394e836ba5cc71891c4840881c5c0..6932b2be338c076e6a3299cb98a727c0578cb990 100644
--- a/gso/workflows/edge_port/validate_edge_port.py
+++ b/gso/workflows/edge_port/validate_edge_port.py
@@ -6,7 +6,7 @@ from orchestrator.targets import Target
 from orchestrator.types import State, UUIDstr
 from orchestrator.utils.errors import ProcessFailureError
 from orchestrator.workflow import StepList, begin, done, step, workflow
-from orchestrator.workflows.steps import resync, store_process_subscription
+from orchestrator.workflows.steps import resync, store_process_subscription, unsync
 from orchestrator.workflows.utils import wrap_modify_initial_input_form
 
 from gso.products.product_types.edge_port import EdgePort
@@ -85,6 +85,7 @@ def validate_edge_port() -> StepList:
     return (
         begin
         >> store_process_subscription(Target.SYSTEM)
+        >> unsync
         >> prepare_state
         >> verify_netbox_entries
         >> anonymous_lso_interaction(verify_base_config)
diff --git a/gso/workflows/iptrunk/create_imported_iptrunk.py b/gso/workflows/iptrunk/create_imported_iptrunk.py
index 5cf9c5f8bfad0b2ff9062e3ec3e058c24b6d9671..722121fe6b04fb120a336b65332c21e69f6d5394 100644
--- a/gso/workflows/iptrunk/create_imported_iptrunk.py
+++ b/gso/workflows/iptrunk/create_imported_iptrunk.py
@@ -20,6 +20,7 @@ from gso.products.product_types.router import Router
 from gso.services import subscriptions
 from gso.services.partners import get_partner_by_name
 from gso.utils.helpers import active_router_selector
+from gso.utils.types.geant_ids import IMPORTED_GA_ID, IMPORTED_GS_ID
 from gso.utils.types.interfaces import LAGMember, LAGMemberList, PhysicalPortCapacity
 
 
@@ -30,7 +31,7 @@ def initial_input_form_generator() -> FormGenerator:
         model_config = ConfigDict(title="Import Iptrunk")
 
         partner: str
-        geant_s_sid: str | None = None
+        gs_id: IMPORTED_GS_ID | None = None
         iptrunk_description: str | None = None
         iptrunk_type: IptrunkType
         iptrunk_speed: PhysicalPortCapacity
@@ -39,12 +40,12 @@ def initial_input_form_generator() -> FormGenerator:
 
         side_a_node_id: active_router_selector()  # type: ignore[valid-type]
         side_a_ae_iface: str
-        side_a_ae_geant_a_sid: str | None = None
+        side_a_ga_id: IMPORTED_GA_ID | None = None
         side_a_ae_members: Annotated[list[LAGMember], AfterValidator(validate_unique_list)]
 
         side_b_node_id: active_router_selector()  # type: ignore[valid-type]
         side_b_ae_iface: str
-        side_b_ae_geant_a_sid: str | None = None
+        side_b_ga_id: IMPORTED_GA_ID | None = None
         side_b_ae_members: Annotated[list[LAGMember], AfterValidator(validate_unique_list)]
 
         iptrunk_ipv4_network: ipaddress.IPv4Network
@@ -71,7 +72,7 @@ def create_subscription(partner: str) -> State:
 @step("Initialize subscription")
 def initialize_subscription(
     subscription: ImportedIptrunkInactive,
-    geant_s_sid: str | None,
+    gs_id: IMPORTED_GS_ID | None,
     iptrunk_type: IptrunkType,
     iptrunk_description: str,
     iptrunk_speed: PhysicalPortCapacity,
@@ -79,15 +80,15 @@ def initialize_subscription(
     iptrunk_isis_metric: int,
     side_a_node_id: str,
     side_a_ae_iface: str,
-    side_a_ae_geant_a_sid: str | None,
+    side_a_ga_id: IMPORTED_GA_ID | None,
     side_a_ae_members: LAGMemberList,
     side_b_node_id: str,
     side_b_ae_iface: str,
-    side_b_ae_geant_a_sid: str | None,
+    side_b_ga_id: IMPORTED_GA_ID | None,
     side_b_ae_members: LAGMemberList,
 ) -> State:
     """Take all input from the user, and store it in the database."""
-    subscription.iptrunk.geant_s_sid = geant_s_sid
+    subscription.iptrunk.gs_id = gs_id
     subscription.iptrunk.iptrunk_description = iptrunk_description
     subscription.iptrunk.iptrunk_type = iptrunk_type
     subscription.iptrunk.iptrunk_speed = iptrunk_speed
@@ -96,7 +97,7 @@ def initialize_subscription(
 
     subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node = Router.from_subscription(side_a_node_id).router
     subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_iface = side_a_ae_iface
-    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_geant_a_sid = side_a_ae_geant_a_sid
+    subscription.iptrunk.iptrunk_sides[0].ga_id = side_a_ga_id
     for member in side_a_ae_members:
         subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members.append(
             IptrunkInterfaceBlockInactive.new(subscription_id=uuid4(), **member),
@@ -104,7 +105,7 @@ def initialize_subscription(
 
     subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node = Router.from_subscription(side_b_node_id).router
     subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_iface = side_b_ae_iface
-    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_geant_a_sid = side_b_ae_geant_a_sid
+    subscription.iptrunk.iptrunk_sides[1].ga_id = side_b_ga_id
     for member in side_b_ae_members:
         subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members.append(
             IptrunkInterfaceBlockInactive.new(subscription_id=uuid4(), **member),
@@ -113,7 +114,7 @@ def initialize_subscription(
         subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_site.site_name,
         subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_site.site_name,
     ])
-    subscription.description = f"IP trunk {side_names[0]} {side_names[1]}, geant_s_sid:{geant_s_sid}"
+    subscription.description = f"IP trunk {side_names[0]} {side_names[1]}, {gs_id}"
     return {"subscription": subscription}
 
 
diff --git a/gso/workflows/iptrunk/create_iptrunk.py b/gso/workflows/iptrunk/create_iptrunk.py
index f8b54c61f43cd303335c1f3eb9fa2057cfee4cc9..cb37490eb1557f866e6ac3ffc550bf68a7978401 100644
--- a/gso/workflows/iptrunk/create_iptrunk.py
+++ b/gso/workflows/iptrunk/create_iptrunk.py
@@ -57,7 +57,11 @@ from gso.services.lso_client import LSOState, lso_interaction
 from gso.services.netbox_client import NetboxClient
 from gso.services.partners import get_partner_by_name
 from gso.services.sharepoint import SharePointClient
-from gso.services.subscriptions import get_non_terminated_iptrunk_subscriptions
+from gso.services.subscriptions import (
+    generate_unique_ga_id,
+    generate_unique_gs_id,
+    get_non_terminated_iptrunk_subscriptions,
+)
 from gso.settings import load_oss_params
 from gso.utils.helpers import (
     available_interfaces_choices,
@@ -86,7 +90,6 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
 
         tt_number: TTNumber
         partner: ReadOnlyField("GEANT", default_type=str)  # type: ignore[valid-type]
-        geant_s_sid: str | None = None
         iptrunk_description: str | None = None
         iptrunk_type: IptrunkType
         iptrunk_speed: PhysicalPortCapacity
@@ -144,7 +147,6 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
         model_config = ConfigDict(title=f"Provide subscription details for side A of the trunk. ({router_a_fqdn})")
 
         side_a_ae_iface: available_lags_choices(router_a) or str  # type: ignore[valid-type]
-        side_a_ae_geant_a_sid: str | None
         side_a_ae_members: ae_members_side_a_type
 
     user_input_side_a = yield CreateIptrunkSideAForm
@@ -182,7 +184,6 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
         model_config = ConfigDict(title=f"Provide subscription details for side B of the trunk. ({router_b_fqdn})")
 
         side_b_ae_iface: available_lags_choices(router_b) or str  # type: ignore[valid-type]
-        side_b_ae_geant_a_sid: str | None
         side_b_ae_members: ae_members_side_b
 
     user_input_side_b = yield CreateIptrunkSideBForm
@@ -196,7 +197,6 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
     )
     summary_form_data = input_forms_data | {"side_a_node": router_a_fqdn, "side_b_node": router_b_fqdn}
     summary_fields = [
-        "geant_s_sid",
         "iptrunk_type",
         "iptrunk_speed",
         "iptrunk_description",
@@ -204,11 +204,9 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
         "side_a_node",
         "side_a_ae_iface",
         "side_a_ae_members",
-        "side_a_ae_geant_a_sid",
         "side_b_node",
         "side_b_ae_iface",
         "side_b_ae_members",
-        "side_b_ae_geant_a_sid",
     ]
     yield from create_summary_form(summary_form_data, product_name, summary_fields)
 
@@ -322,25 +320,23 @@ def ping_all_hosts_v6(new_ipv6_network: str) -> State:
 @step("Initialize subscription")
 def initialize_subscription(
     subscription: IptrunkInactive,
-    geant_s_sid: str | None,
     iptrunk_type: IptrunkType,
     iptrunk_description: str | None,
     iptrunk_speed: PhysicalPortCapacity,
     iptrunk_minimum_links: int,
     side_a_node_id: str,
     side_a_ae_iface: str,
-    side_a_ae_geant_a_sid: str | None,
     side_a_ae_members: list[dict],
     side_b_node_id: str,
     side_b_ae_iface: str,
-    side_b_ae_geant_a_sid: str | None,
     side_b_ae_members: list[dict],
 ) -> State:
     """Take all input from the user, and store it in the database."""
     oss_params = load_oss_params()
     side_a = Router.from_subscription(side_a_node_id).router
     side_b = Router.from_subscription(side_b_node_id).router
-    subscription.iptrunk.geant_s_sid = geant_s_sid
+    gs_id = generate_unique_gs_id()
+    subscription.iptrunk.gs_id = gs_id
     subscription.iptrunk.iptrunk_description = iptrunk_description
     subscription.iptrunk.iptrunk_type = iptrunk_type
     subscription.iptrunk.iptrunk_speed = iptrunk_speed
@@ -349,7 +345,8 @@ def initialize_subscription(
 
     subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node = side_a
     subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_iface = side_a_ae_iface
-    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_geant_a_sid = side_a_ae_geant_a_sid
+    side_a_ga_id = generate_unique_ga_id()
+    subscription.iptrunk.iptrunk_sides[0].ga_id = side_a_ga_id
     for member in side_a_ae_members:
         subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members.append(
             IptrunkInterfaceBlockInactive.new(subscription_id=uuid4(), **member),
@@ -357,13 +354,14 @@ def initialize_subscription(
 
     subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node = side_b
     subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_iface = side_b_ae_iface
-    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_geant_a_sid = side_b_ae_geant_a_sid
+    side_b_ga_id = generate_unique_ga_id()
+    subscription.iptrunk.iptrunk_sides[1].ga_id = side_b_ga_id
     for member in side_b_ae_members:
         subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members.append(
             IptrunkInterfaceBlockInactive.new(subscription_id=uuid4(), **member),
         )
     side_names = sorted([side_a.router_site.site_name, side_b.router_site.site_name])
-    subscription.description = f"IP trunk {side_names[0]} {side_names[1]}, geant_s_sid:{geant_s_sid}"
+    subscription.description = f"IP trunk {side_names[0]} {side_names[1]}, {gs_id}"
 
     return {"subscription": subscription}
 
@@ -377,7 +375,7 @@ def provision_ip_trunk_iface_dry(subscription: IptrunkInactive, process_id: UUID
         "verb": "deploy",
         "config_object": "trunk_interface",
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Deploy config for "
-        f"{subscription.iptrunk.geant_s_sid}",
+        f"{subscription.iptrunk.gs_id}",
     }
 
     return {
@@ -403,7 +401,7 @@ def provision_ip_trunk_iface_real(subscription: IptrunkInactive, process_id: UUI
         "verb": "deploy",
         "config_object": "trunk_interface",
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Deploy config for "
-        f"{subscription.iptrunk.geant_s_sid}",
+        f"{subscription.iptrunk.gs_id}",
     }
 
     return {
@@ -441,7 +439,7 @@ def provision_ip_trunk_isis_iface_dry(subscription: IptrunkInactive, process_id:
         "verb": "deploy",
         "config_object": "isis_interface",
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Deploy config for "
-        f"{subscription.iptrunk.geant_s_sid}",
+        f"{subscription.iptrunk.gs_id}",
     }
 
     return {
@@ -467,7 +465,7 @@ def provision_ip_trunk_isis_iface_real(subscription: IptrunkInactive, process_id
         "verb": "deploy",
         "config_object": "isis_interface",
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Deploy config for "
-        f"{subscription.iptrunk.geant_s_sid}",
+        f"{subscription.iptrunk.gs_id}",
     }
 
     return {
@@ -507,7 +505,9 @@ def register_dns_records(subscription: IptrunkInactive) -> State:
         ipv4_addr = subscription.iptrunk.iptrunk_ipv4_network[index]
         ipv6_addr = subscription.iptrunk.iptrunk_ipv6_network[index + 1]
 
-        infoblox.create_host_by_ip(fqdn, ipv4_addr, ipv6_addr, "TRUNK", str(subscription.subscription_id))
+        infoblox.create_host_by_ip(
+            fqdn, "TRUNK", str(subscription.subscription_id), ipv4_address=ipv4_addr, ipv6_address=ipv6_addr
+        )
 
     return {"subscription": subscription}
 
@@ -574,7 +574,7 @@ def create_new_sharepoint_checklist(subscription: IptrunkProvisioning, tt_number
     new_list_item_url = SharePointClient().add_list_item(
         list_name="ip_trunk",
         fields={
-            "Title": f"{subscription.description} - {subscription.iptrunk.geant_s_sid}",
+            "Title": f"{subscription.description} - {subscription.iptrunk.gs_id}",
             "TT_NUMBER": tt_number,
             "GAP_PROCESS_URL": f"{load_oss_params().GENERAL.public_hostname}/workflows/{process_id}",
             "ACTIVITY_TYPE": "Creation",
diff --git a/gso/workflows/iptrunk/migrate_iptrunk.py b/gso/workflows/iptrunk/migrate_iptrunk.py
index 5684dd2d10dc042032e1523e54e4b5657ba247c2..83e7000c5c3376bd12934ce5d64b8ab52661fd94 100644
--- a/gso/workflows/iptrunk/migrate_iptrunk.py
+++ b/gso/workflows/iptrunk/migrate_iptrunk.py
@@ -50,7 +50,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
     """Gather input from the operator on the new router that the IP trunk should connect to."""
     subscription = Iptrunk.from_subscription(subscription_id)
     form_title = (
-        f"Subscription {subscription.iptrunk.geant_s_sid} "
+        f"Subscription {subscription.iptrunk.gs_id} "
         f" from {subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}"
         f" to {subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}"
     )
@@ -298,7 +298,7 @@ def disable_old_config_dry(
         "config_object": "deactivate",
         "dry_run": True,
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} "
-        f"- Deploy config for {subscription.iptrunk.geant_s_sid}",
+        f"- Deploy config for {subscription.iptrunk.gs_id}",
     }
 
     return {
@@ -337,7 +337,7 @@ def disable_old_config_real(
         "config_object": "deactivate",
         "dry_run": False,
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} "
-        f"- Deploy config for {subscription.iptrunk.geant_s_sid}",
+        f"- Deploy config for {subscription.iptrunk.gs_id}",
     }
 
     return {
@@ -376,7 +376,7 @@ def deploy_new_config_dry(
         "config_object": "trunk_interface",
         "dry_run": True,
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} "
-        f"- Deploy config for {subscription.iptrunk.geant_s_sid}",
+        f"- Deploy config for {subscription.iptrunk.gs_id}",
     }
 
     return {
@@ -415,7 +415,7 @@ def deploy_new_config_real(
         "config_object": "trunk_interface",
         "dry_run": False,
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} "
-        f"- Deploy config for {subscription.iptrunk.geant_s_sid}",
+        f"- Deploy config for {subscription.iptrunk.gs_id}",
     }
 
     return {
@@ -445,7 +445,7 @@ def update_remaining_side_bfd_dry(
         "verb": "update",
         "config_object": "bfd_update",
         "dry_run": True,
-        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} " f"- Update BFD config.",
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Update BFD config.",
     }
 
     return {
@@ -473,7 +473,7 @@ def update_remaining_side_bfd_real(
         "verb": "update",
         "config_object": "bfd_update",
         "dry_run": False,
-        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} " f"- Update BFD config.",
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Update BFD config.",
     }
 
     return {
@@ -564,7 +564,7 @@ def deploy_new_isis(
         "config_object": "isis_interface",
         "dry_run": False,
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} "
-        f"- Deploy config for {subscription.iptrunk.geant_s_sid}",
+        f"- Deploy config for {subscription.iptrunk.gs_id}",
     }
 
     return {
@@ -629,7 +629,7 @@ def restore_isis_metric(
         "verb": "deploy",
         "config_object": "isis_interface",
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Deploy config for "
-        f"{subscription.iptrunk.geant_s_sid}",
+        f"{subscription.iptrunk.gs_id}",
     }
 
     return {
@@ -668,7 +668,7 @@ def delete_old_config_dry(
         "config_object": "delete",
         "dry_run": True,
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} "
-        f"- Deploy config for {subscription.iptrunk.geant_s_sid}",
+        f"- Deploy config for {subscription.iptrunk.gs_id}",
     }
 
     return {
@@ -707,7 +707,7 @@ def delete_old_config_real(
         "config_object": "delete",
         "dry_run": False,
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} "
-        f"- Deploy config for {subscription.iptrunk.geant_s_sid}",
+        f"- Deploy config for {subscription.iptrunk.gs_id}",
     }
 
     return {
@@ -740,7 +740,7 @@ def update_ipam(subscription: Iptrunk, replace_index: int, new_node: Router, new
     #  And in with the new
     new_fqdn = f"{new_lag_interface}-0.{new_node.router.router_fqdn}"
     comment = str(subscription.subscription_id)
-    infoblox.create_host_by_ip(new_fqdn, v4_addr, v6_addr, "TRUNK", comment)
+    infoblox.create_host_by_ip(new_fqdn, "TRUNK", comment, ipv4_address=v4_addr, ipv6_address=v6_addr)
 
     return {"subscription": subscription}
 
@@ -769,9 +769,7 @@ def update_subscription_model(
         subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_site.site_name,
         subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_site.site_name,
     ])
-    subscription.description = (
-        f"IP trunk {side_names[0]} {side_names[1]}, geant_s_sid:{subscription.iptrunk.geant_s_sid}"
-    )
+    subscription.description = f"IP trunk {side_names[0]} {side_names[1]}, {subscription.iptrunk.gs_id}"
 
     return {"subscription": subscription}
 
@@ -816,7 +814,7 @@ def create_new_sharepoint_checklist(subscription: Iptrunk, tt_number: str, proce
     new_list_item_url = SharePointClient().add_list_item(
         list_name="ip_trunk",
         fields={
-            "Title": f"{subscription.description} - {subscription.iptrunk.geant_s_sid}",
+            "Title": f"{subscription.description} - {subscription.iptrunk.gs_id}",
             "TT_NUMBER": tt_number,
             "GAP_PROCESS_URL": f"{load_oss_params().GENERAL.public_hostname}/workflows/{process_id}",
             "ACTIVITY_TYPE": "Migration",
diff --git a/gso/workflows/iptrunk/modify_isis_metric.py b/gso/workflows/iptrunk/modify_isis_metric.py
index 61d2f0908bc80fb12981c276cf2d3d45c7b12944..80d97531e6cc61f53e94d19e2980fd795aa628cf 100644
--- a/gso/workflows/iptrunk/modify_isis_metric.py
+++ b/gso/workflows/iptrunk/modify_isis_metric.py
@@ -53,7 +53,7 @@ def provision_ip_trunk_isis_iface_dry(subscription: Iptrunk, process_id: UUIDstr
         "verb": "deploy",
         "config_object": "isis_interface",
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Deploy config for "
-        f"{subscription.iptrunk.geant_s_sid}",
+        f"{subscription.iptrunk.gs_id}",
     }
 
     return {
@@ -79,7 +79,7 @@ def provision_ip_trunk_isis_iface_real(subscription: Iptrunk, process_id: UUIDst
         "verb": "deploy",
         "config_object": "isis_interface",
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Deploy config for "
-        f"{subscription.iptrunk.geant_s_sid}",
+        f"{subscription.iptrunk.gs_id}",
     }
 
     return {
diff --git a/gso/workflows/iptrunk/modify_trunk_interface.py b/gso/workflows/iptrunk/modify_trunk_interface.py
index 9c1be5cd451dadb4a039c869c3f9f67cf32836de..ea2309e4dd7249583f1ddd4472258ecb862697bd 100644
--- a/gso/workflows/iptrunk/modify_trunk_interface.py
+++ b/gso/workflows/iptrunk/modify_trunk_interface.py
@@ -7,6 +7,7 @@ necessary modifications will be applied.
 """
 
 import json
+from functools import partial
 from typing import Annotated
 from uuid import UUID, uuid4
 
@@ -18,7 +19,7 @@ from orchestrator.utils.json import json_dumps
 from orchestrator.workflow import StepList, begin, conditional, done, step, workflow
 from orchestrator.workflows.steps import resync, store_process_subscription, unsync
 from orchestrator.workflows.utils import wrap_modify_initial_input_form
-from pydantic import ConfigDict
+from pydantic import AfterValidator, ConfigDict, Field
 from pydantic_forms.validators import Label, ReadOnlyField
 
 from gso.products.product_blocks.iptrunk import (
@@ -39,6 +40,7 @@ from gso.utils.shared_enums import Vendor
 from gso.utils.types.interfaces import JuniperLAGMember, LAGMember, LAGMemberList, PhysicalPortCapacity
 from gso.utils.types.ip_address import IPv4AddressType, IPv6AddressType
 from gso.utils.types.tt_number import TTNumber
+from gso.utils.types.unique_field import validate_field_is_unique
 from gso.workflows.iptrunk.migrate_iptrunk import check_ip_trunk_optical_levels_pre
 from gso.workflows.iptrunk.validate_iptrunk import check_ip_trunk_isis
 
@@ -86,7 +88,12 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
 
     class ModifyIptrunkForm(FormPage):
         tt_number: TTNumber
-        geant_s_sid: str | None = subscription.iptrunk.geant_s_sid
+        gs_id: (
+            Annotated[
+                str, AfterValidator(partial(validate_field_is_unique, subscription_id)), Field(pattern=r"^GS-\d{5}$")
+            ]
+            | None
+        ) = subscription.iptrunk.gs_id
         iptrunk_description: str | None = subscription.iptrunk.iptrunk_description
         iptrunk_type: IptrunkType = subscription.iptrunk.iptrunk_type
         warning_label: Label = (
@@ -125,7 +132,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
             subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn, default_type=str
         )
         side_a_ae_iface: ReadOnlyField(subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_iface, default_type=str)  # type: ignore[valid-type]
-        side_a_ae_geant_a_sid: str | None = subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_geant_a_sid
+        side_a_ga_id: str | None = subscription.iptrunk.iptrunk_sides[0].ga_id
         side_a_ae_members: ae_members_side_a = (  # type: ignore[valid-type]
             subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members
             if initial_user_input.iptrunk_speed == subscription.iptrunk.iptrunk_speed
@@ -142,7 +149,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
             subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn, default_type=str
         )
         side_b_ae_iface: ReadOnlyField(subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_iface, default_type=str)  # type: ignore[valid-type]
-        side_b_ae_geant_a_sid: str | None = subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_geant_a_sid
+        side_b_ga_id: str | None = subscription.iptrunk.iptrunk_sides[1].ga_id
         side_b_ae_members: ae_members_side_b = (  # type: ignore[valid-type]
             subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members
             if initial_user_input.iptrunk_speed == subscription.iptrunk.iptrunk_speed
@@ -254,14 +261,14 @@ def update_side_members(subscription: Iptrunk, side_index: int, new_members: lis
 @step("Update subscription")
 def modify_iptrunk_subscription(
     subscription: Iptrunk,
-    geant_s_sid: str | None,
+    gs_id: str | None,
     iptrunk_type: IptrunkType,
     iptrunk_description: str | None,
     iptrunk_speed: PhysicalPortCapacity,
     iptrunk_minimum_links: int,
-    side_a_ae_geant_a_sid: str | None,
+    side_a_ga_id: str | None,
     side_a_ae_members: list[dict],
-    side_b_ae_geant_a_sid: str | None,
+    side_b_ga_id: str | None,
     side_b_ae_members: list[dict],
 ) -> State:
     """Modify the subscription in the service database, reflecting the changes to the newly selected interfaces."""
@@ -287,22 +294,22 @@ def modify_iptrunk_subscription(
             if ae_member["interface_name"] not in [m["interface_name"] for m in current_members]
         ])
     # Update the subscription
-    subscription.iptrunk.geant_s_sid = geant_s_sid
+    subscription.iptrunk.gs_id = gs_id
     subscription.iptrunk.iptrunk_description = iptrunk_description
     subscription.iptrunk.iptrunk_type = iptrunk_type
     subscription.iptrunk.iptrunk_speed = iptrunk_speed
     subscription.iptrunk.iptrunk_minimum_links = iptrunk_minimum_links
 
-    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_geant_a_sid = side_a_ae_geant_a_sid
+    subscription.iptrunk.iptrunk_sides[0].ga_id = side_a_ga_id
     update_side_members(subscription, 0, side_a_ae_members)
-    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_geant_a_sid = side_b_ae_geant_a_sid
+    subscription.iptrunk.iptrunk_sides[1].ga_id = side_b_ga_id
     update_side_members(subscription, 1, side_b_ae_members)
 
     side_names = sorted([
         subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_site.site_name,
         subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_site.site_name,
     ])
-    subscription.description = f"IP trunk {side_names[0]} {side_names[1]}, geant_s_sid:{geant_s_sid}"
+    subscription.description = f"IP trunk {side_names[0]} {side_names[1]}, {gs_id}"
 
     return {
         "subscription": subscription,
@@ -323,7 +330,7 @@ def provision_ip_trunk_iface_dry(
         "config_object": "trunk_interface",
         "removed_ae_members": removed_ae_members,
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Deploy config for "
-        f"{subscription.iptrunk.geant_s_sid}",
+        f"{subscription.iptrunk.gs_id}",
     }
 
     return {
@@ -352,7 +359,7 @@ def provision_ip_trunk_iface_real(
         "config_object": "trunk_interface",
         "removed_ae_members": removed_ae_members,
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Deploy config for "
-        f"{subscription.iptrunk.geant_s_sid}",
+        f"{subscription.iptrunk.gs_id}",
     }
 
     return {
diff --git a/gso/workflows/iptrunk/terminate_iptrunk.py b/gso/workflows/iptrunk/terminate_iptrunk.py
index d008ad88bfcfdbf3484f5056d522deddf565554d..24cb42719a09b5483bdff3d9fb73e819b88e7eef 100644
--- a/gso/workflows/iptrunk/terminate_iptrunk.py
+++ b/gso/workflows/iptrunk/terminate_iptrunk.py
@@ -70,7 +70,7 @@ def deprovision_ip_trunk_dry(subscription: Iptrunk, process_id: UUIDstr, tt_numb
         "verb": "terminate",
         "config_object": "trunk_deprovision",
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} "
-        f"- Remove config for {subscription.iptrunk.geant_s_sid}",
+        f"- Remove config for {subscription.iptrunk.gs_id}",
     }
 
     return {
@@ -96,7 +96,7 @@ def deprovision_ip_trunk_real(subscription: Iptrunk, process_id: UUIDstr, tt_num
         "verb": "terminate",
         "config_object": "trunk_deprovision",
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} "
-        f"- Remove config for {subscription.iptrunk.geant_s_sid}",
+        f"- Remove config for {subscription.iptrunk.gs_id}",
     }
 
     return {
diff --git a/gso/workflows/l2_circuit/create_imported_layer_2_circuit.py b/gso/workflows/l2_circuit/create_imported_layer_2_circuit.py
index a0fc39cb3afffb338b939996fb339e8d8659430e..3e5e97c992ce33c7fb0fe5300c0a1f5e0034d072 100644
--- a/gso/workflows/l2_circuit/create_imported_layer_2_circuit.py
+++ b/gso/workflows/l2_circuit/create_imported_layer_2_circuit.py
@@ -23,6 +23,7 @@ from gso.products.product_types.layer_2_circuit import (
 from gso.services.partners import get_partner_by_name
 from gso.services.subscriptions import get_product_id_by_name
 from gso.utils.shared_enums import SBPType
+from gso.utils.types.geant_ids import IMPORTED_GS_ID
 from gso.utils.types.interfaces import BandwidthString
 from gso.utils.types.virtual_identifiers import VC_ID, VLAN_ID
 
@@ -39,7 +40,7 @@ def initial_input_form_generator() -> FormGenerator:
 
         service_type: Layer2CircuitServiceType
         partner: str
-        geant_sid: str
+        gs_id: IMPORTED_GS_ID
         vc_id: VC_ID
         layer_2_circuit_side_a: ServiceBindingPortInput
         layer_2_circuit_side_b: ServiceBindingPortInput
@@ -91,7 +92,7 @@ def create_subscription(partner: str, service_type: Layer2CircuitServiceType) ->
 @step("Initialize subscription")
 def initialize_subscription(
     subscription: ImportedLayer2CircuitInactive,
-    geant_sid: str,
+    gs_id: str,
     layer_2_circuit_side_a: dict[str, Any],
     layer_2_circuit_side_b: dict[str, Any],
     layer_2_circuit_type: Layer2CircuitType,
@@ -110,7 +111,7 @@ def initialize_subscription(
             edge_port=EdgePort.from_subscription(subscription_id=circuit_side_data["edge_port"]).edge_port,
             sbp_type=SBPType.L2,
             vlan_id=circuit_side_data["vlan_id"],
-            geant_sid=geant_sid,
+            gs_id=gs_id,
             is_tagged=layer_2_circuit_type == Layer2CircuitType.TAGGED,
             custom_firewall_filters=False,
         )
diff --git a/gso/workflows/l2_circuit/create_layer_2_circuit.py b/gso/workflows/l2_circuit/create_layer_2_circuit.py
index 0a15fcebc00172a30e223b7c975badbcbd46d144..acf5beefa74860e548d272598d37f10fa3d69221 100644
--- a/gso/workflows/l2_circuit/create_layer_2_circuit.py
+++ b/gso/workflows/l2_circuit/create_layer_2_circuit.py
@@ -19,6 +19,7 @@ from gso.products.product_blocks.service_binding_port import ServiceBindingPortI
 from gso.products.product_types.edge_port import EdgePort
 from gso.products.product_types.layer_2_circuit import Layer2Circuit, Layer2CircuitInactive
 from gso.services.partners import get_partner_by_name
+from gso.services.subscriptions import generate_unique_gs_id
 from gso.utils.helpers import active_edge_port_selector, generate_unique_vc_id, partner_choice
 from gso.utils.shared_enums import SBPType
 from gso.utils.types.interfaces import BandwidthString
@@ -69,7 +70,6 @@ def initial_input_generator(product_name: str) -> FormGenerator:
         vlan_divider: Divider = Field(None, exclude=True)
         policer_bandwidth: _policer_field(policer_enabled=initial_user_input.policer_enabled)  # type: ignore[valid-type]
         policer_burst_rate: _policer_field(policer_enabled=initial_user_input.policer_enabled)  # type: ignore[valid-type]
-        geant_sid: str
         layer_2_circuit_side_a: Layer2CircuitSideSelection
         side_divider: Divider = Field(None, exclude=True)
         layer_2_circuit_side_b: Layer2CircuitSideSelection
@@ -105,17 +105,17 @@ def initialize_subscription(
     policer_enabled: bool,  # noqa: FBT001
     policer_bandwidth: BandwidthString | None,
     policer_burst_rate: BandwidthString | None,
-    geant_sid: str,
 ) -> State:
     """Build a subscription object from all user input."""
     layer_2_circuit_sides = []
+    gs_id = generate_unique_gs_id()
     for circuit_side_data in [layer_2_circuit_side_a, layer_2_circuit_side_b]:
         sbp = ServiceBindingPortInactive.new(
             uuid4(),
             edge_port=EdgePort.from_subscription(subscription_id=circuit_side_data["edge_port"]).edge_port,
             sbp_type=SBPType.L2,
             vlan_id=circuit_side_data["vlan_id"],
-            geant_sid=geant_sid,
+            gs_id=gs_id,
             is_tagged=layer_2_circuit_type == Layer2CircuitType.TAGGED,
             custom_firewall_filters=False,
         )
diff --git a/gso/workflows/l3_core_service/create_imported_l3_core_service.py b/gso/workflows/l3_core_service/create_imported_l3_core_service.py
index dd77021bbd511fe6dec291279680e36cf618678d..a4948f7dc378668b077eb1e72be0b0b8452e8dfc 100644
--- a/gso/workflows/l3_core_service/create_imported_l3_core_service.py
+++ b/gso/workflows/l3_core_service/create_imported_l3_core_service.py
@@ -21,7 +21,8 @@ from gso.products.product_types.l3_core_service import ImportedL3CoreServiceInac
 from gso.services.partners import get_partner_by_name
 from gso.services.subscriptions import get_product_id_by_name
 from gso.utils.shared_enums import SBPType
-from gso.utils.types.ip_address import IPAddress, IPv4AddressType, IPV4Netmask, IPv6AddressType, IPV6Netmask
+from gso.utils.types.geant_ids import IMPORTED_GS_ID
+from gso.utils.types.ip_address import IPAddress, IPv4AddressType, IPv4Netmask, IPv6AddressType, IPv6Netmask
 from gso.utils.types.virtual_identifiers import VLAN_ID
 
 
@@ -50,15 +51,15 @@ def initial_input_form_generator() -> FormGenerator:
     class ServiceBindingPort(BaseModel):
         edge_port: UUIDstr
         ap_type: str
-        geant_sid: str
+        gs_id: IMPORTED_GS_ID
         sbp_type: SBPType = SBPType.L3
         is_tagged: bool = False
         vlan_id: VLAN_ID
         custom_firewall_filters: bool = False
         ipv4_address: IPv4AddressType
-        ipv4_mask: IPV4Netmask
+        ipv4_mask: IPv4Netmask
         ipv6_address: IPv6AddressType
-        ipv6_mask: IPV6Netmask
+        ipv6_mask: IPv6Netmask
         rtbh_enabled: bool = True
         is_multi_hop: bool = True
         bgp_peers: list[BaseBGPPeer]
diff --git a/gso/workflows/l3_core_service/create_l3_core_service.py b/gso/workflows/l3_core_service/create_l3_core_service.py
index b45883830e66f3b937eb6f0a69136daf35e6fbc1..3cf358d2316173bcd3d6c8dadc83bbf6a2857c44 100644
--- a/gso/workflows/l3_core_service/create_l3_core_service.py
+++ b/gso/workflows/l3_core_service/create_l3_core_service.py
@@ -20,12 +20,13 @@ from gso.products.product_types.edge_port import EdgePort
 from gso.products.product_types.l3_core_service import L3CoreService, L3CoreServiceInactive
 from gso.services.lso_client import LSOState, lso_interaction
 from gso.services.partners import get_partner_by_id
+from gso.services.subscriptions import generate_unique_gs_id
 from gso.utils.helpers import (
     active_edge_port_selector,
     partner_choice,
 )
 from gso.utils.shared_enums import APType, SBPType
-from gso.utils.types.ip_address import IPv4AddressType, IPV4Netmask, IPv6AddressType, IPV6Netmask
+from gso.utils.types.ip_address import IPv4AddressType, IPv4Netmask, IPv6AddressType, IPv6Netmask
 from gso.utils.types.tt_number import TTNumber
 from gso.utils.types.virtual_identifiers import VLAN_ID
 from gso.utils.workflow_steps import start_moodi, stop_moodi
@@ -113,19 +114,18 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
             exclude=True,
         )
 
-        geant_sid: str
         is_tagged: bool = False
         vlan_id: VLAN_ID
         custom_firewall_filters: bool = False
         divider: Divider = Field(None, exclude=True)
         v4_label: Label = Field("IPV4 SBP interface params", exclude=True)
         ipv4_address: IPv4AddressType
-        ipv4_mask: IPV4Netmask
+        ipv4_mask: IPv4Netmask
         v4_bfd_settings: BFDSettingsForm
         divider2: Divider = Field(None, exclude=True)
         v6_label: Label = Field("IPV6 SBP interface params", exclude=True)
         ipv6_address: IPv6AddressType
-        ipv6_mask: IPV6Netmask
+        ipv6_mask: IPv6Netmask
         v6_bfd_settings: BFDSettingsForm
         divider3: Divider = Field(None, exclude=True)
         v4_bgp_peer: IPv4BGPPeer
@@ -165,6 +165,7 @@ def initialize_subscription(
         BGPSession.new(subscription_id=uuid4(), rtbh_enabled=True, is_multi_hop=True, **session)
         for session in binding_port_input["bgp_peers"]
     ]
+    sbp_gs_id = generate_unique_gs_id()
     service_binding_port = ServiceBindingPortInactive.new(
         subscription_id=uuid4(),
         v4_bfd_settings=BFDSettings.new(subscription_id=uuid4(), **(binding_port_input.pop("v4_bfd_settings"))),
@@ -173,6 +174,7 @@ def initialize_subscription(
         bgp_session_list=sbp_bgp_session_list,
         sbp_type=SBPType.L3,
         edge_port=edge_port_subscription.edge_port,
+        gs_id=sbp_gs_id,
     )
     subscription.l3_core_service.ap_list.append(
         AccessPortInactive.new(
@@ -183,8 +185,8 @@ def initialize_subscription(
     )
     edge_port_fqdn_list.append(edge_port_subscription.edge_port.node.router_fqdn)
 
-    subscription.description = f"{product_name} service"
     partner_name = get_partner_by_id(subscription.customer_id).name
+    subscription.description = f"{product_name} service for {partner_name}"
     return {"subscription": subscription, "edge_port_fqdn_list": edge_port_fqdn_list, "partner_name": partner_name}
 
 
diff --git a/gso/workflows/l3_core_service/modify_l3_core_service.py b/gso/workflows/l3_core_service/modify_l3_core_service.py
index 7dbeb4860ff5c322e1b6714572db2ab4fa6c8815..af3218c54f88d082d87fa89698e6ec833485dcf7 100644
--- a/gso/workflows/l3_core_service/modify_l3_core_service.py
+++ b/gso/workflows/l3_core_service/modify_l3_core_service.py
@@ -21,7 +21,7 @@ from gso.products.product_types.edge_port import EdgePort
 from gso.products.product_types.l3_core_service import L3CoreService
 from gso.utils.helpers import active_edge_port_selector
 from gso.utils.shared_enums import APType, SBPType
-from gso.utils.types.ip_address import IPv4AddressType, IPV4Netmask, IPv6AddressType, IPV6Netmask
+from gso.utils.types.ip_address import IPv4AddressType, IPv4Netmask, IPv6AddressType, IPv6Netmask
 from gso.utils.types.virtual_identifiers import VLAN_ID
 
 
@@ -152,15 +152,15 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
                 exclude=True,
             )
 
-            geant_sid: str = current_sbp.geant_sid
+            gs_id: str = current_sbp.gs_id
             is_tagged: bool = current_sbp.is_tagged
             # The SBP model does not require these five fields, but in the case of GÉANT IP or IAS this will never
             # occur since it's a layer 3 service. The ignore statements are there to put our type checker at ease.
             vlan_id: VLAN_ID = current_sbp.vlan_id  # type: ignore[assignment]
             ipv4_address: IPv4AddressType = current_sbp.ipv4_address  # type: ignore[assignment]
-            ipv4_mask: IPV4Netmask = current_sbp.ipv4_mask  # type: ignore[assignment]
+            ipv4_mask: IPv4Netmask = current_sbp.ipv4_mask  # type: ignore[assignment]
             ipv6_address: IPv6AddressType = current_sbp.ipv6_address  # type: ignore[assignment]
-            ipv6_mask: IPV6Netmask = current_sbp.ipv6_mask  # type: ignore[assignment]
+            ipv6_mask: IPv6Netmask = current_sbp.ipv6_mask  # type: ignore[assignment]
             custom_firewall_filters: bool = current_sbp.custom_firewall_filters
             v4_bfd_settings: BFDInputModel = BFDInputModel(
                 bfd_enabled=current_sbp.v4_bfd_settings.bfd_enabled,
@@ -212,7 +212,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
                 exclude=True,
             )
 
-            geant_sid: str
+            gs_id: str
             is_tagged: bool = False
             vlan_id: VLAN_ID
             ipv4_address: IPv4AddressType
@@ -279,7 +279,7 @@ def modify_existing_sbp_blocks(subscription: L3CoreService, modified_sbp_list: l
 
         current_sbp.bgp_session_list = [v4_peer, v6_peer]
         current_sbp.vlan_id = modified_sbp_data["vlan_id"]
-        current_sbp.geant_sid = modified_sbp_data["geant_sid"]
+        current_sbp.gs_id = modified_sbp_data["gs_id"]
         current_sbp.is_tagged = modified_sbp_data["is_tagged"]
         current_sbp.ipv4_address = modified_sbp_data["ipv4_address"]
         current_sbp.ipv6_address = modified_sbp_data["ipv6_address"]
diff --git a/gso/workflows/lan_switch_interconnect/create_imported_lan_switch_interconnect.py b/gso/workflows/lan_switch_interconnect/create_imported_lan_switch_interconnect.py
index ae26125f37beb8b876a1edbc691e7c37f9787fed..f89c37ffff15c282534384d72766fd497a9e852f 100644
--- a/gso/workflows/lan_switch_interconnect/create_imported_lan_switch_interconnect.py
+++ b/gso/workflows/lan_switch_interconnect/create_imported_lan_switch_interconnect.py
@@ -21,15 +21,19 @@ from gso.products.product_types.router import Router
 from gso.products.product_types.switch import Switch
 from gso.services.partners import get_partner_by_name
 from gso.services.subscriptions import get_product_id_by_name
-from gso.utils.types.ip_address import AddressSpace, IPv4NetworkType
+from gso.utils.types.virtual_identifiers import (
+    DEFAULT_DCN_MANAGEMENT_VLAN_ID,
+    DEFAULT_SWITCH_MANAGEMENT_VLAN_ID,
+    VLAN_ID,
+)
 
 
 def _initial_input_form_generator() -> FormGenerator:
     class ImportLanSwitchInterconnect(FormPage):
         lan_switch_interconnect_description: str
-        lan_switch_interconnect_ip_network: IPv4NetworkType | None
-        address_space: AddressSpace
         minimum_links: int
+        switch_management_vlan_id: VLAN_ID | None = DEFAULT_SWITCH_MANAGEMENT_VLAN_ID
+        dcn_management_vlan_id: VLAN_ID | None = DEFAULT_DCN_MANAGEMENT_VLAN_ID
         router_side: LanSwitchInterconnectRouterSideImportModel
         switch_side: LanSwitchInterconnectSwitchSideImportModel
 
@@ -51,17 +55,17 @@ def create_subscription(partner: str) -> State:
 def initialize_subscription(
     subscription: ImportedLanSwitchInterconnectInactive,
     lan_switch_interconnect_description: str,
-    lan_switch_interconnect_ip_network: IPv4NetworkType | None,
-    address_space: AddressSpace,
     minimum_links: int,
+    switch_management_vlan_id: VLAN_ID,
+    dcn_management_vlan_id: VLAN_ID,
     router_side: dict,
     switch_side: dict,
 ) -> State:
     """Initialize the subscription using input data."""
     subscription.lan_switch_interconnect.lan_switch_interconnect_description = lan_switch_interconnect_description
-    subscription.lan_switch_interconnect.lan_switch_interconnect_ip_network = lan_switch_interconnect_ip_network
-    subscription.lan_switch_interconnect.address_space = address_space
     subscription.lan_switch_interconnect.minimum_links = minimum_links
+    subscription.lan_switch_interconnect.switch_management_vlan_id = switch_management_vlan_id
+    subscription.lan_switch_interconnect.dcn_management_vlan_id = dcn_management_vlan_id
 
     router_block = Router.from_subscription(router_side.pop("node")).router
     router_side_interfaces = [
diff --git a/gso/workflows/lan_switch_interconnect/create_lan_switch_interconnect.py b/gso/workflows/lan_switch_interconnect/create_lan_switch_interconnect.py
index eeec84e17d08e53cf3bf9fcf3531f38314772c11..1e8c3bc58cedd1eeb4d16bf5f0cfbc7be414c192 100644
--- a/gso/workflows/lan_switch_interconnect/create_lan_switch_interconnect.py
+++ b/gso/workflows/lan_switch_interconnect/create_lan_switch_interconnect.py
@@ -1,5 +1,6 @@
 """A creation workflow for creating a new interconnect between a switch and a router."""
 
+from ipaddress import IPv4Network, IPv6Network
 from typing import Annotated
 from uuid import uuid4
 
@@ -7,11 +8,12 @@ from annotated_types import Len
 from orchestrator.forms import FormPage
 from orchestrator.targets import Target
 from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr
+from orchestrator.utils.errors import ProcessFailureError
 from orchestrator.workflow import StepList, begin, done, step, workflow
 from orchestrator.workflows.steps import resync, set_status, store_process_subscription
 from orchestrator.workflows.utils import wrap_create_initial_input_form
 from pydantic import AfterValidator, ConfigDict
-from pydantic_forms.validators import Divider, ReadOnlyField
+from pydantic_forms.validators import ReadOnlyField
 
 from gso.products.product_blocks.lan_switch_interconnect import (
     LanSwitchInterconnectInterfaceBlockInactive,
@@ -19,12 +21,16 @@ from gso.products.product_blocks.lan_switch_interconnect import (
 from gso.products.product_types.lan_switch_interconnect import LanSwitchInterconnectInactive
 from gso.products.product_types.router import Router
 from gso.products.product_types.switch import Switch
+from gso.services.infoblox import create_host_by_ip, create_v4_network_by_ip, create_v6_network_by_ip
 from gso.services.partners import get_partner_by_name
+from gso.settings import load_oss_params
 from gso.utils.helpers import (
     active_router_selector,
     active_switch_selector,
     available_interfaces_choices,
     available_lags_choices,
+    generate_lan_switch_interconnect_subnet_v4,
+    generate_lan_switch_interconnect_subnet_v6,
 )
 from gso.utils.shared_enums import Vendor
 from gso.utils.types.interfaces import (
@@ -35,8 +41,8 @@ from gso.utils.types.interfaces import (
     PhysicalPortCapacity,
     validate_interface_names_are_unique,
 )
-from gso.utils.types.ip_address import AddressSpace
 from gso.utils.types.tt_number import TTNumber
+from gso.utils.types.virtual_identifiers import DEFAULT_DCN_MANAGEMENT_VLAN_ID, DEFAULT_SWITCH_MANAGEMENT_VLAN_ID
 from gso.workflows.shared import create_summary_form
 
 
@@ -48,11 +54,8 @@ def _initial_input_form(product_name: str) -> FormGenerator:
         partner: ReadOnlyField("GEANT", default_type=str)  # type: ignore[valid-type]
         router_side: active_router_selector()  # type: ignore[valid-type]
         switch_side: active_switch_selector()  # type: ignore[valid-type]
-        address_space: AddressSpace
         description: str
         minimum_link_count: int
-        divider: Divider
-        vlan_id: ReadOnlyField(111, default_type=int)  # type: ignore[valid-type]
 
     initial_input = yield CreateLANSwitchInterconnectForm
     router = Router.from_subscription(initial_input.router_side)
@@ -107,10 +110,8 @@ def _initial_input_form(product_name: str) -> FormGenerator:
         "partner",
         "router_side",
         "switch_side",
-        "address_space",
         "description",
         "minimum_link_count",
-        "vlan_id",
         "router_side_iface",
         "router_side_ae_members",
         "switch_side_iface",
@@ -133,7 +134,6 @@ def create_subscription(product: UUIDstr, partner: str) -> State:
 def initialize_subscription(
     subscription: LanSwitchInterconnectInactive,
     description: str,
-    address_space: AddressSpace,
     minimum_link_count: int,
     router_side: UUIDstr,
     router_side_iface: JuniperPhyInterface,
@@ -144,8 +144,8 @@ def initialize_subscription(
 ) -> State:
     """Update the product model with all input from the operator."""
     subscription.lan_switch_interconnect.lan_switch_interconnect_description = description
-    subscription.lan_switch_interconnect.address_space = address_space
     subscription.lan_switch_interconnect.minimum_links = minimum_link_count
+    subscription.lan_switch_interconnect.switch_management_vlan_id = DEFAULT_SWITCH_MANAGEMENT_VLAN_ID
     subscription.lan_switch_interconnect.router_side.node = Router.from_subscription(router_side).router
     subscription.lan_switch_interconnect.router_side.ae_iface = router_side_iface
     for member in router_side_ae_members:
@@ -158,10 +158,90 @@ def initialize_subscription(
         subscription.lan_switch_interconnect.switch_side.ae_members.append(
             LanSwitchInterconnectInterfaceBlockInactive.new(subscription_id=uuid4(), **member)
         )
+    if subscription.lan_switch_interconnect.router_side.node.router_site.site_contains_optical_equipment:
+        subscription.lan_switch_interconnect.dcn_management_vlan_id = DEFAULT_DCN_MANAGEMENT_VLAN_ID
 
     return {"subscription": subscription}
 
 
+@step("Register IPv4 network in IPAM")
+def register_dns_records_v4_network(subscription: LanSwitchInterconnectInactive) -> State:
+    """Add DNS records in IPAM."""
+    router_site = subscription.lan_switch_interconnect.router_side.node.router_site
+    if not router_site or not router_site.site_internal_id:
+        msg = "Site internal ID not set. Cannot continue."
+        raise ProcessFailureError(msg, details=router_site)
+
+    new_network = generate_lan_switch_interconnect_subnet_v4(router_site.site_internal_id)
+    ipam_oss_params = load_oss_params().IPAM.LAN_SWITCH_INTERCONNECT
+    create_v4_network_by_ip(
+        ipam_oss_params.dns_view, ipam_oss_params.network_view, new_network, str(subscription.subscription_id)
+    )
+
+    return {"ipam_registrations": {"v4": {"network": new_network}}}
+
+
+@step("Register IPv4 devices in IPAM")
+def register_dns_records_v4_devices(
+    subscription: LanSwitchInterconnectInactive, subscription_id: UUIDstr, ipam_registrations: dict[str, dict[str, str]]
+) -> State:
+    """Register DNS records for both switch and router side in IPAM."""
+    switch_hostname = subscription.lan_switch_interconnect.switch_side.switch.fqdn
+    router_hostname = (
+        f"{subscription.lan_switch_interconnect.router_side.ae_iface}."
+        f"{subscription.lan_switch_interconnect.router_side.node.router_fqdn}"
+    )
+    if not (switch_hostname and router_hostname):
+        msg = "Missing switch or router hostname, cannot continue."
+        raise ProcessFailureError(msg, details=subscription.lan_switch_interconnect)
+
+    ip_network = IPv4Network(ipam_registrations["v4"]["network"])
+
+    create_host_by_ip(switch_hostname, "LAN_SWITCH_INTERCONNECT", subscription_id, ipv4_address=ip_network[10])
+    create_host_by_ip(router_hostname, "LAN_SWITCH_INTERCONNECT", subscription_id, ipv4_address=ip_network[1])
+
+    return {"ipam_registrations": {"v4": {switch_hostname: ip_network[10], router_hostname: ip_network[1]}}}
+
+
+@step("Register IPv6 network in IPAM")
+def register_dns_records_v6_network(subscription: LanSwitchInterconnectInactive) -> State:
+    """Add DNS records in IPAM."""
+    router_site = subscription.lan_switch_interconnect.router_side.node.router_site
+    if not router_site or not router_site.site_internal_id:
+        msg = "Site internal ID not set. Cannot continue."
+        raise ProcessFailureError(msg, details=router_site)
+
+    new_network = generate_lan_switch_interconnect_subnet_v6(router_site.site_internal_id)
+    ipam_oss_params = load_oss_params().IPAM.LAN_SWITCH_INTERCONNECT
+    create_v6_network_by_ip(
+        ipam_oss_params.dns_view, ipam_oss_params.network_view, new_network, str(subscription.subscription_id)
+    )
+
+    return {"ipam_registrations": {"v6": {"network": new_network}}}
+
+
+@step("Register IPv6 devices in IPAM")
+def register_dns_records_v6_devices(
+    subscription: LanSwitchInterconnectInactive, subscription_id: UUIDstr, ipam_registrations: dict[str, dict[str, str]]
+) -> State:
+    """Register DNS records for both switch and router side in IPAM."""
+    switch_hostname = subscription.lan_switch_interconnect.switch_side.switch.fqdn
+    router_hostname = (
+        f"{subscription.lan_switch_interconnect.router_side.ae_iface}."
+        f"{subscription.lan_switch_interconnect.router_side.node.router_fqdn}"
+    )
+    if not (switch_hostname and router_hostname):
+        msg = "Missing switch or router hostname, cannot continue."
+        raise ProcessFailureError(msg, details=subscription.lan_switch_interconnect)
+
+    ip_network = IPv6Network(ipam_registrations["v6"]["network"])
+
+    create_host_by_ip(switch_hostname, "LAN_SWITCH_INTERCONNECT", subscription_id, ipv6_address=ip_network[10])
+    create_host_by_ip(router_hostname, "LAN_SWITCH_INTERCONNECT", subscription_id, ipv6_address=ip_network[1])
+
+    return {"ipam_registrations": {"v6": {switch_hostname: ip_network[10], router_hostname: ip_network[1]}}}
+
+
 @workflow(
     "Create LAN Switch Interconnect",
     initial_input_form=wrap_create_initial_input_form(_initial_input_form),
@@ -174,6 +254,10 @@ def create_lan_switch_interconnect() -> StepList:
         >> create_subscription
         >> store_process_subscription(Target.CREATE)
         >> initialize_subscription
+        >> register_dns_records_v4_network
+        >> register_dns_records_v4_devices
+        >> register_dns_records_v6_network
+        >> register_dns_records_v6_devices
         >> set_status(SubscriptionLifecycle.ACTIVE)
         >> resync
         >> done
diff --git a/gso/workflows/lan_switch_interconnect/terminate_lan_switch_interconnect.py b/gso/workflows/lan_switch_interconnect/terminate_lan_switch_interconnect.py
index ac9aef3f6d75d868555f8b23d6a9ea3c03b1a71f..5403d07fa15622600558e3a58ec027e532e465cf 100644
--- a/gso/workflows/lan_switch_interconnect/terminate_lan_switch_interconnect.py
+++ b/gso/workflows/lan_switch_interconnect/terminate_lan_switch_interconnect.py
@@ -4,13 +4,15 @@ from orchestrator import begin, workflow
 from orchestrator.forms import FormPage
 from orchestrator.targets import Target
 from orchestrator.types import SubscriptionLifecycle, UUIDstr
-from orchestrator.workflow import StepList, done
+from orchestrator.workflow import StepList, done, step
 from orchestrator.workflows.steps import resync, set_status, store_process_subscription, unsync
 from orchestrator.workflows.utils import wrap_modify_initial_input_form
 from pydantic_forms.types import FormGenerator
 from pydantic_forms.validators import Label
 
 from gso.products.product_types.lan_switch_interconnect import LanSwitchInterconnect
+from gso.services.infoblox import delete_host_by_fqdn, delete_network
+from gso.utils.helpers import generate_lan_switch_interconnect_subnet_v4
 from gso.utils.types.tt_number import TTNumber
 
 
@@ -31,6 +33,25 @@ def _input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
     return {"subscription": lan_switch_interconnect}
 
 
+@step("Release IPAM resources")
+def clean_up_ipam(subscription: LanSwitchInterconnect) -> None:
+    """Clear up :term:`IPAM` resources in :term:`DNS`.
+
+    First, remove :term:`DNS` records for both the router and switch side of the interconnect.
+    Second, remove the registered network for this interconnect.
+    """
+    delete_host_by_fqdn(subscription.lan_switch_interconnect.switch_side.switch.fqdn)
+    delete_host_by_fqdn(
+        f"{subscription.lan_switch_interconnect.router_side.ae_iface}."
+        f"{subscription.lan_switch_interconnect.router_side.node.router_fqdn}"
+    )
+    delete_network(
+        generate_lan_switch_interconnect_subnet_v4(
+            subscription.lan_switch_interconnect.router_side.node.router_site.site_internal_id
+        )
+    )
+
+
 @workflow(
     "Terminate LAN Switch Interconnect",
     initial_input_form=wrap_modify_initial_input_form(_input_form_generator),
@@ -42,6 +63,7 @@ def terminate_lan_switch_interconnect() -> StepList:
         begin
         >> store_process_subscription(Target.TERMINATE)
         >> unsync
+        >> clean_up_ipam
         >> set_status(SubscriptionLifecycle.TERMINATED)
         >> resync
         >> done
diff --git a/gso/workflows/lan_switch_interconnect/validate_lan_switch_interconnect.py b/gso/workflows/lan_switch_interconnect/validate_lan_switch_interconnect.py
index 9e6cfdd36d5d58c54e7b5319ed129932b068093c..b97a601a6f501fbe5a0c52fc97009232de28afce 100644
--- a/gso/workflows/lan_switch_interconnect/validate_lan_switch_interconnect.py
+++ b/gso/workflows/lan_switch_interconnect/validate_lan_switch_interconnect.py
@@ -3,11 +3,39 @@
 from typing import Any
 
 from orchestrator.targets import Target
+from orchestrator.utils.errors import ProcessFailureError
 from orchestrator.workflow import StepList, begin, done, step, workflow
 from orchestrator.workflows.steps import resync, store_process_subscription, unsync
 from orchestrator.workflows.utils import wrap_modify_initial_input_form
 
+from gso.products.product_types.lan_switch_interconnect import LanSwitchInterconnect
+from gso.services.infoblox import find_host_by_fqdn, find_network_by_cidr
 from gso.services.lso_client import LSOState, anonymous_lso_interaction
+from gso.utils.helpers import generate_lan_switch_interconnect_subnet_v4
+
+
+@step("Validate IPAM configuration")
+def validate_ipam(subscription: LanSwitchInterconnect) -> None:
+    """Validate that :term:`DNS` records are registered in :term:`IPAM` correctly."""
+    switch_fqdn = subscription.lan_switch_interconnect.switch_side.switch.fqdn
+    router_fqdn = (
+        f"{subscription.lan_switch_interconnect.router_side.ae_iface}."
+        f"{subscription.lan_switch_interconnect.router_side.node.router_fqdn}"
+    )
+
+    for fqdn in [switch_fqdn, router_fqdn]:
+        host_record = find_host_by_fqdn(fqdn)
+        if not host_record or str(subscription.subscription_id) not in host_record.comment:
+            msg = "DNS record is incorrectly configured in IPAM, please investigate this manually!"
+            raise ProcessFailureError(msg, details=host_record)
+
+    lan_interconnect_network = generate_lan_switch_interconnect_subnet_v4(
+        subscription.lan_switch_interconnect.router_side.node.router_site.site_internal_id
+    )
+    network_record = find_network_by_cidr(lan_interconnect_network)
+    if not network_record or str(subscription.subscription_id) not in network_record.comment:
+        msg = "LAN Switch Interconnect network is incorrectly configured in IPAM, please investigate this manually!"
+        raise ProcessFailureError(msg, details=network_record)
 
 
 @step("Check config for drift")
@@ -15,7 +43,14 @@ def validate_config(subscription: dict[str, Any]) -> LSOState:
     """Workflow step for running a playbook that checks whether config has drifted."""
     return {
         "playbook_name": "lan_switch_interconnect.yaml",
-        "inventory": {"all": {"hosts": {None: None}}},
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription["lan_switch_interconnect"]["switch_side"]["switch"]["fqdn"]: None,
+                    subscription["lan_switch_interconnect"]["router_side"]["node"]["router_fqdn"]: None,
+                }
+            }
+        },
         "extra_vars": {
             "subscription_json": subscription,
             "verb": "deploy",
@@ -34,6 +69,7 @@ def validate_lan_switch_interconnect() -> StepList:
         begin
         >> store_process_subscription(Target.SYSTEM)
         >> unsync
+        >> validate_ipam
         >> anonymous_lso_interaction(validate_config)
         >> resync
         >> done
diff --git a/gso/workflows/router/validate_router.py b/gso/workflows/router/validate_router.py
index b029db47ab9233f1f3624628852f2692abbf5aec..89a05004d48742ebdecff943b95f2914a651edba 100644
--- a/gso/workflows/router/validate_router.py
+++ b/gso/workflows/router/validate_router.py
@@ -55,7 +55,7 @@ def check_netbox_entry_exists(subscription: Router) -> None:
 
 @step("Verify P BGP P-ONLY neighbors")
 def verify_p_ibgp(subscription: dict[str, Any]) -> LSOState:
-    """Verify PE neighbors in P-ONLY group on a P router."""
+    """Verify PE neighbors in `P-ONLY` group on a P router."""
     extra_vars = {
         "dry_run": True,
         "subscription": subscription,
@@ -100,7 +100,7 @@ def verify_pe_mesh_in_pe(subscription: dict[str, Any]) -> LSOState:
 
 @step("Verify PE BGP P-ONLY neighbors")
 def verify_all_p_in_pe(subscription: dict[str, Any]) -> LSOState:
-    """Verify P neighbors in P-ONLY group on a PE router."""
+    """Verify P neighbors in `P-ONLY` group on a PE router."""
     extra_vars = {
         "dry_run": True,
         "subscription": subscription,
diff --git a/gso/workflows/site/create_imported_site.py b/gso/workflows/site/create_imported_site.py
index b11908638cae008d14a4d1c31b693b174c80db89..d3c1720b0d76daa0e05c1f59f44aeca153c1cdae 100644
--- a/gso/workflows/site/create_imported_site.py
+++ b/gso/workflows/site/create_imported_site.py
@@ -55,6 +55,7 @@ def initialize_subscription(
     site_internal_id: int,
     site_ts_address: IPAddress,
     site_tier: SiteTier,
+    site_contains_optical_equipment: bool,  # noqa: FBT001
 ) -> State:
     """Initialise the subscription object with all input."""
     subscription.site.site_name = site_name
@@ -67,6 +68,7 @@ def initialize_subscription(
     subscription.site.site_internal_id = site_internal_id
     subscription.site.site_tier = site_tier
     subscription.site.site_ts_address = site_ts_address
+    subscription.site.site_contains_optical_equipment = site_contains_optical_equipment
 
     subscription.description = f"Site in {site_city}, {site_country}"
 
diff --git a/gso/workflows/site/create_site.py b/gso/workflows/site/create_site.py
index 6bf4bd7405d8a47b317e34b77e5170484f00e962..9fe35fc175ad03cfdce30853e3d175edede6ee12 100644
--- a/gso/workflows/site/create_site.py
+++ b/gso/workflows/site/create_site.py
@@ -85,6 +85,7 @@ def initialize_subscription(
     subscription.site.site_internal_id = site_internal_id
     subscription.site.site_tier = site_tier
     subscription.site.site_ts_address = site_ts_address
+    subscription.site.site_contains_optical_equipment = True
 
     subscription.description = f"Site in {site_city}, {site_country}"
 
diff --git a/gso/workflows/site/terminate_site.py b/gso/workflows/site/terminate_site.py
index 8f13e3589f2c9b04737567448fb8265587fd97a1..92da5ccae5479a50211e7cf89958991a1c9ceadb 100644
--- a/gso/workflows/site/terminate_site.py
+++ b/gso/workflows/site/terminate_site.py
@@ -1,8 +1,8 @@
 """A workflow for terminating a site subscription.
 
 The `terminate_site` workflow will take an existing and active site subscription from an `ACTIVE` to a `TERMINATED`
-state. This requires all dependant subscription instances to already be terminated. If this is not the case, the
-workflow will be unavailable for an operator to run, accompanied by an error message explaining this fact.
+state. This requires all dependant subscription instances to already be terminated. If not, the workflow will be
+unavailable for an operator to run, accompanied by an error message explaining this fact.
 """
 
 from orchestrator.forms import FormPage
diff --git a/gso/workflows/switch/create_switch.py b/gso/workflows/switch/create_switch.py
index 0074c8e4cc870a3afaffd8ac704759841dcf9a52..0a8d27935be45658f4cf925b73197ec8242ccb7f 100644
--- a/gso/workflows/switch/create_switch.py
+++ b/gso/workflows/switch/create_switch.py
@@ -10,7 +10,7 @@ from orchestrator.workflow import StepList, begin, done, inputstep, step, workfl
 from orchestrator.workflows.steps import resync, set_status, store_process_subscription
 from orchestrator.workflows.utils import wrap_create_initial_input_form
 from pydantic import ConfigDict, model_validator
-from pydantic_forms.validators import Label, ReadOnlyField
+from pydantic_forms.validators import Choice, Label, ReadOnlyField
 
 from gso.products.product_blocks.switch import SwitchModel
 from gso.products.product_types.site import Site
@@ -40,7 +40,7 @@ def _initial_input_form_generator(product_name: str) -> FormGenerator:
         hostname: str
         ts_port: PortNumber
         vendor: ReadOnlyField(Vendor.JUNIPER, default_type=Vendor)  # type: ignore[valid-type]
-        model: ReadOnlyField(SwitchModel.EX3400, default_type=SwitchModel)  # type: ignore[valid-type]
+        model: SwitchModel = Choice("Switch model", SwitchModel.values())  # type: ignore[assignment, arg-type]
 
         @model_validator(mode="after")
         def hostname_must_be_available(self) -> Self:
diff --git a/gso/workflows/tasks/validate_geant_products.py b/gso/workflows/tasks/validate_geant_products.py
index a7bfcdaaaaca2a1b7d3b84766629829716530e3b..8fe39f61b762f46683336f802af1885d447ab339 100644
--- a/gso/workflows/tasks/validate_geant_products.py
+++ b/gso/workflows/tasks/validate_geant_products.py
@@ -1,6 +1,6 @@
 """A task that checks for all products in the database to be well-kept."""
 
-# .. vale off
+# <!-- vale off -->
 # Copyright 2019-2020 SURF.
 # Copyright 2024 GÉANT Vereniging.
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,7 +14,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-# .. vale on
+# <!-- vale on -->
 
 from orchestrator.targets import Target
 from orchestrator.workflow import StepList, done, init, workflow
diff --git a/gso/workflows/vrf/__init__.py b/gso/workflows/vrf/__init__.py
index 0fc8180f6bd9547693b518647a82b21fe636b1db..68a1b462970d258956f93a8b45e4823528928136 100644
--- a/gso/workflows/vrf/__init__.py
+++ b/gso/workflows/vrf/__init__.py
@@ -1 +1 @@
-""":term:`VRF` Virtual Routing and Forwarding."""
+"""VRF Virtual Routing and Forwarding."""
diff --git a/gso/workflows/vrf/create_vrf.py b/gso/workflows/vrf/create_vrf.py
index 9ca01d1e81b3f9babd27a024b827c26df41ba2cb..8da4f2d91a9584090a3d2b1fd5b53489a1dc1438 100644
--- a/gso/workflows/vrf/create_vrf.py
+++ b/gso/workflows/vrf/create_vrf.py
@@ -1,4 +1,4 @@
-"""A creation workflow for adding a new virtual routing and forwarding (:term:`VRF`) service."""
+"""A creation workflow for adding a new virtual routing and forwarding (VRF) service."""
 
 from orchestrator.forms import FormPage
 from orchestrator.targets import Target
@@ -39,7 +39,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
 
 @step("Create subscription")
 def create_subscription(product: UUIDstr, partner: str) -> State:
-    """Create a new :term:`VRF` subscription."""
+    """Create a new VRF subscription."""
     subscription = VRFInactive.from_product_id(product, get_partner_by_name(partner)["partner_id"])
 
     return {
@@ -72,7 +72,7 @@ def initialize_subscription(
     target=Target.CREATE,
 )
 def create_vrf() -> StepList:
-    """Create a virtual routing and forwarding (:term:`VRF`) service."""
+    """Create a virtual routing and forwarding (VRF) service."""
     return (
         begin
         >> create_subscription
diff --git a/gso/workflows/vrf/modify_vrf_router_list.py b/gso/workflows/vrf/modify_vrf_router_list.py
index b0618d95237be5ff3455953033edea3377fa0346..efa7e531edf856673aa04cec984b307ce843b8e8 100644
--- a/gso/workflows/vrf/modify_vrf_router_list.py
+++ b/gso/workflows/vrf/modify_vrf_router_list.py
@@ -1,6 +1,6 @@
-"""Modify :term:`VRF` to add or remove routers."""
+"""Modify VRF to add or remove routers."""
 
-from typing import Annotated
+from typing import Annotated, Any
 
 from orchestrator.forms import FormPage
 from orchestrator.targets import Target
@@ -13,11 +13,13 @@ from pydantic_forms.validators import validate_unique_list
 
 from gso.products.product_types.router import Router
 from gso.products.product_types.vrf import VRF
+from gso.services.lso_client import LSOState, lso_interaction
 from gso.utils.helpers import active_router_selector
+from gso.utils.types.tt_number import TTNumber
 
 
 def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
-    """Modify :term:`VRF` to add or remove routers."""
+    """Modify VRF to add or remove routers."""
     subscription = VRF.from_subscription(subscription_id)
 
     class RouterSelection(BaseModel):
@@ -25,6 +27,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
 
     class ModifyVRFRouterListForm(FormPage):
         model_config = ConfigDict(title=f"Modify the {subscription.vrf.vrf_name} VRF to add or remove routers.")
+        tt_number: TTNumber
 
         router_list: Annotated[
             list[RouterSelection],
@@ -51,11 +54,62 @@ def update_subscription_model(subscription: VRF, router_list: list[dict[str, UUI
     return {"subscription": subscription}
 
 
+@step("[DRY RUN] Update VRF on list of routers")
+def update_vrf_on_routers_dry(
+    subscription: dict[str, Any], process_id: UUIDstr, tt_number: str, router_list: list[dict[str, UUIDstr]]
+) -> LSOState:
+    """Deploy VRF on a list of routers - Dry run."""
+    vrf_new_router_list = [Router.from_subscription(router["router_id"]) for router in router_list]
+    inventory = {"all": {"hosts": {router.router.router_fqdn: None for router in vrf_new_router_list}}}
+    extra_vars = {
+        "subscription": subscription,
+        "dry_run": True,
+        "verb": "update",
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - "
+        f"Deploy config for {subscription["description"]}",
+    }
+    return {
+        "playbook_name": "gap_ansible/playbooks/vrf_update.yaml",
+        "inventory": inventory,
+        "extra_vars": extra_vars,
+    }
+
+
+@step("[FOR REAL] Update VRF on list of routers")
+def update_vrf_on_routers_real(
+    subscription: dict[str, Any], process_id: UUIDstr, tt_number: str, router_list: list[dict[str, UUIDstr]]
+) -> LSOState:
+    """Deploy VRF on a list of routers - with commit."""
+    vrf_new_router_list = [Router.from_subscription(router["router_id"]) for router in router_list]
+    inventory = {"all": {"hosts": {router.router.router_fqdn: None for router in vrf_new_router_list}}}
+    extra_vars = {
+        "subscription": subscription,
+        "dry_run": False,
+        "verb": "update",
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - "
+        f"Deploy config for {subscription["description"]}",
+    }
+    return {
+        "playbook_name": "gap_ansible/playbooks/vrf_update.yaml",
+        "inventory": inventory,
+        "extra_vars": extra_vars,
+    }
+
+
 @workflow(
     "Modify VRF router list",
     initial_input_form=wrap_modify_initial_input_form(initial_input_form_generator),
     target=Target.MODIFY,
 )
 def modify_vrf_router_list() -> StepList:
-    """Modify the :term:`VRF` router list."""
-    return begin >> store_process_subscription(Target.MODIFY) >> unsync >> update_subscription_model >> resync >> done
+    """Modify the VRF router list."""
+    return (
+        begin
+        >> store_process_subscription(Target.MODIFY)
+        >> unsync
+        >> lso_interaction(update_vrf_on_routers_dry)
+        >> lso_interaction(update_vrf_on_routers_real)
+        >> update_subscription_model
+        >> resync
+        >> done
+    )
diff --git a/requirements.txt b/requirements.txt
index 2488de134a518a88436df822e7a57e5519ee8935..97fd1fedbe08dc83073afba7953b608a5fd2a189 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,6 +8,7 @@ celery==5.3.6
 azure-identity==1.16.0
 msgraph-sdk==1.2.0
 ping3==4.0.8
+unidecode==1.3.8
 
 # Test and linting dependencies
 celery-stubs==0.1.3
diff --git a/setup.py b/setup.py
index 63422f812cd604e37e3c54ee1e308b2df4380493..bce5bfc27bfdab4845b989f98d31dc31f276530f 100644
--- a/setup.py
+++ b/setup.py
@@ -4,7 +4,7 @@ from setuptools import find_packages, setup
 
 setup(
     name="geant-service-orchestrator",
-    version="2.27",
+    version="2.28",
     author="GÉANT Orchestration and Automation Team",
     author_email="goat@geant.org",
     description="GÉANT Service Orchestrator",
@@ -21,6 +21,7 @@ setup(
         "azure-identity==1.16.0",
         "msgraph-sdk==1.2.0",
         "ping3==4.0.8",
+        "unidecode==1.3.8",
     ],
     include_package_data=True,
 )
diff --git a/sonar.properties b/sonar.properties
index 4933ccded6da45048827c9c5c748ce232806cd70..cea633fcce5f5f68771bed8d097568fd195ac865 100644
--- a/sonar.properties
+++ b/sonar.properties
@@ -1,6 +1,7 @@
 sonar.projectKey=gso
 sonar.projectName=GSO
-sonar.projectVersion=0.x
+sonar.projectVersion=2.28
 sonar.sources=gso
 sonar.python.coverage.reportPaths=coverage.xml
-sonar.host.url=https://sonarqube.software.geant.org/
\ No newline at end of file
+sonar.host.url=https://sonarqube.software.geant.org/
+sonar.exclusions=gso/migrations/**, test/**, docs/**
diff --git a/start-worker.sh b/start-worker.sh
index 92cd6304d95db2a697ca1b77e4e705c5b9350b1a..3c18dd4422ae6d60e16c78f45aa7189659dd5ca9 100755
--- a/start-worker.sh
+++ b/start-worker.sh
@@ -4,4 +4,4 @@ set -o errexit
 set -o nounset
 
 cd /app
-python -m celery -A gso.worker worker --loglevel=info --concurrency=1 --pool=solo
+python -m celery -A gso.worker worker --loglevel=info
diff --git a/test/cli/test_imports.py b/test/cli/test_imports.py
index fa378703d422aa49287eb0ff8f8c5c3794ae677f..a1b0c75702f18c59ad30e2c3711fe695fb912d95 100644
--- a/test/cli/test_imports.py
+++ b/test/cli/test_imports.py
@@ -30,7 +30,6 @@ from gso.products.product_types.site import Site
 from gso.utils.helpers import generate_unique_vc_id, iso_from_ipv4
 from gso.utils.shared_enums import Vendor
 from gso.utils.types.interfaces import PhysicalPortCapacity
-from gso.utils.types.ip_address import AddressSpace
 
 
 ##############
@@ -62,7 +61,7 @@ def iptrunk_data(temp_file, router_subscription_factory, faker) -> (Path, dict):
         ipv6_network = ipv6_network or str(faker.ipv6_network(max_subnet=126))
 
         iptrunk_data = {
-            "id": faker.geant_sid(),
+            "id": faker.imported_gs_id(),
             "config": {
                 "common": {
                     "link_speed": PhysicalPortCapacity.HUNDRED_GIGABIT_PER_SECOND,
@@ -73,7 +72,7 @@ def iptrunk_data(temp_file, router_subscription_factory, faker) -> (Path, dict):
                 "nodeA": {
                     "name": side_a_node or Router.from_subscription(router_side_a).router.router_fqdn,
                     "ae_name": side_a_ae_name or faker.network_interface(),
-                    "port_sid": faker.geant_sid(),
+                    "port_ga_id": faker.imported_ga_id(),
                     "members": side_a_members
                     or [
                         {
@@ -88,7 +87,7 @@ def iptrunk_data(temp_file, router_subscription_factory, faker) -> (Path, dict):
                 "nodeB": {
                     "name": side_b_node or Router.from_subscription(router_side_b).router.router_fqdn,
                     "ae_name": side_b_ae_name or faker.network_interface(),
-                    "port_sid": faker.geant_sid(),
+                    "port_ga_id": faker.imported_ga_id(),
                     "members": side_b_members
                     or [
                         {
@@ -215,7 +214,7 @@ def switch_data(temp_file, faker, site_subscription_factory):
             "ts_port": faker.port_number(is_user=True),
             "site": site_subscription_factory(),
             "switch_vendor": Vendor.JUNIPER,
-            "switch_model": SwitchModel.EX3400,
+            "switch_model": SwitchModel.EX3400_48T,
         }
         switch_data.update(**kwargs)
 
@@ -230,8 +229,6 @@ def lan_switch_interconnect_data(temp_file, faker, switch_subscription_factory,
     def _lan_switch_interconnect_data(**kwargs):
         lan_switch_interconnect_data = {
             "lan_switch_interconnect_description": faker.sentence(),
-            "lan_switch_interconnect_ip_network": str(faker.ipv4_network()),
-            "address_space": AddressSpace.PUBLIC,
             "minimum_links": 1,
             "router_side": {
                 "node": router_subscription_factory(),
@@ -240,7 +237,6 @@ def lan_switch_interconnect_data(temp_file, faker, switch_subscription_factory,
                     {"interface_name": faker.network_interface(), "interface_description": faker.sentence()}
                     for _ in range(2)
                 ],
-                "ipv4_address": faker.ipv4(),
             },
             "switch_side": {
                 "switch": switch_subscription_factory(),
@@ -249,7 +245,6 @@ def lan_switch_interconnect_data(temp_file, faker, switch_subscription_factory,
                     {"interface_name": faker.network_interface(), "interface_description": faker.sentence()}
                     for _ in range(2)
                 ],
-                "ipv4_address": faker.ipv4(),
             },
         }
         lan_switch_interconnect_data.update(**kwargs)
@@ -270,7 +265,7 @@ def edge_port_data(temp_file, faker, router_subscription_factory, partner_factor
             "encapsulation": EncapsulationType.DOT1Q,
             "name": "lag34",
             "minimum_links": 2,
-            "geant_ga_id": faker.geant_gid(),
+            "ga_id": faker.imported_ga_id(),
             "mac_address": faker.mac_address(),
             "partner": partner_factory()["name"],
             "enable_lacp": True,
@@ -305,7 +300,7 @@ def l3_core_service_data(temp_file, faker, partner_factory, edge_port_subscripti
                 {
                     "edge_port": edge_port_subscription_factory(),
                     "ap_type": "PRIMARY",
-                    "geant_sid": faker.geant_sid(),
+                    "gs_id": faker.imported_gs_id(),
                     "vlan_id": faker.vlan_id(),
                     "ipv4_address": faker.ipv4(),
                     "ipv4_mask": faker.ipv4_netmask(),
@@ -353,7 +348,7 @@ def l3_core_service_data(temp_file, faker, partner_factory, edge_port_subscripti
                 {
                     "edge_port": edge_port_subscription_factory(),
                     "ap_type": "BACKUP",
-                    "geant_sid": faker.geant_sid(),
+                    "gs_id": faker.imported_gs_id(),
                     "vlan_id": faker.vlan_id(),
                     "ipv4_address": faker.ipv4(),
                     "ipv4_mask": faker.ipv4_netmask(),
@@ -414,7 +409,7 @@ def layer_2_circuit_data(temp_file, faker, partner_factory, edge_port_subscripti
         layer_2_circuit_input_data = {
             "partner": partner_factory()["name"],
             "service_type": Layer2CircuitServiceType.GEANT_PLUS,
-            "geant_sid": faker.geant_sid(),
+            "gs_id": faker.imported_gs_id(),
             "vc_id": generate_unique_vc_id(),
             "layer_2_circuit_side_a": {
                 "edge_port": edge_port_subscription_factory(),
@@ -697,7 +692,7 @@ def test_import_l3_core_service_with_invalid_edge_port(
             {
                 "edge_port": fake_uuid,
                 "ap_type": "PRIMARY",
-                "geant_sid": faker.geant_sid(),
+                "gs_id": faker.imported_gs_id(),
                 "vlan_id": faker.vlan_id(),
                 "ipv4_address": faker.ipv4(),
                 "ipv4_mask": faker.ipv4_netmask(),
@@ -737,7 +732,7 @@ def test_import_l3_core_service_with_invalid_edge_port(
             {
                 "edge_port": edge_port_subscription_factory(),
                 "ap_type": "BACKUP",
-                "geant_sid": faker.geant_sid(),
+                "gs_id": faker.imported_gs_id(),
                 "vlan_id": faker.vlan_id(),
                 "ipv4_address": faker.ipv4(),
                 "ipv4_mask": faker.ipv4_netmask(),
diff --git a/test/conftest.py b/test/conftest.py
index da558c8a693e9a71cd2f30ff5832eac42b49684c..4548fe0fadd99957fee38e96fc0874402063f048 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -73,11 +73,21 @@ class FakerProvider(BaseProvider):
 
         return f"TT#{random_date}{random_int}"
 
-    def geant_gid(self) -> str:
-        return self.generator.numerify("GID-#####")
+    def ga_id(self) -> str:
+        random_int = self.generator.random_int(min=50000, max=99999)
+        return f"GA-{random_int}"
 
-    def geant_sid(self) -> str:
-        return self.generator.numerify("SID-#####")
+    def gs_id(self) -> str:
+        random_int = self.generator.random_int(min=50000, max=99999)
+        return f"GS-{random_int}"
+
+    def imported_ga_id(self) -> str:
+        random_int = self.generator.random_int(min=00000, max=50000)
+        return f"GA-{random_int}"
+
+    def imported_gs_id(self) -> str:
+        random_int = self.generator.random_int(min=00000, max=50000)
+        return f"GS-{random_int}"
 
     def site_name(self) -> str:
         site_name = "".join(self.generator.random_letter().upper() for _ in range(3))
diff --git a/test/fixtures/edge_port_fixtures.py b/test/fixtures/edge_port_fixtures.py
index 669d42ff90b4e0894ca1d117dde58448daab0903..0ea8744983148d97f4c9920498ea2da8a3b17ff7 100644
--- a/test/fixtures/edge_port_fixtures.py
+++ b/test/fixtures/edge_port_fixtures.py
@@ -30,7 +30,7 @@ def edge_port_subscription_factory(faker, partner_factory, router_subscription_f
         member_speed=PhysicalPortCapacity.HUNDRED_GIGABIT_PER_SECOND,
         minimum_links=None,
         edge_port_type=EdgePortType.PUBLIC,
-        geant_ga_id=None,
+        ga_id=None,
         edge_port_ae_members=None,
         status: SubscriptionLifecycle | None = None,
         *,
@@ -52,7 +52,7 @@ def edge_port_subscription_factory(faker, partner_factory, router_subscription_f
             )
 
         edge_port_subscription.edge_port.edge_port_description = description or faker.text(max_nb_chars=30)
-        edge_port_subscription.edge_port.geant_ga_id = geant_ga_id or faker.geant_sid()
+        edge_port_subscription.edge_port.ga_id = ga_id or faker.ga_id()
         edge_port_subscription.edge_port.node = node or node
         edge_port_subscription.edge_port.edge_port_name = name or f"lag-{faker.pyint(21, 50)}"
         edge_port_subscription.edge_port.edge_port_description = edge_port_description or faker.sentence()
diff --git a/test/fixtures/iptrunk_fixtures.py b/test/fixtures/iptrunk_fixtures.py
index 5044a2e5904c92023e264e9c5f40cac8a82cb773..742e974a98001ee4908f08b6502d7d5ca9ae8c20 100644
--- a/test/fixtures/iptrunk_fixtures.py
+++ b/test/fixtures/iptrunk_fixtures.py
@@ -21,7 +21,7 @@ def iptrunk_side_subscription_factory(router_subscription_factory, faker):
     def subscription_create(
         iptrunk_side_node=None,
         iptrunk_side_ae_iface=None,
-        iptrunk_side_ae_geant_a_sid=None,
+        ga_id=None,
         iptrunk_side_ae_members=None,
         iptrunk_side_ae_members_description=None,
     ) -> IptrunkSideBlock:
@@ -31,7 +31,7 @@ def iptrunk_side_subscription_factory(router_subscription_factory, faker):
                 iptrunk_side_node or router_subscription_factory(vendor=Vendor.NOKIA)
             ).router,
             iptrunk_side_ae_iface=iptrunk_side_ae_iface or faker.pystr(),
-            iptrunk_side_ae_geant_a_sid=iptrunk_side_ae_geant_a_sid or faker.geant_sid(),
+            ga_id=ga_id or faker.ga_id(),
             iptrunk_side_ae_members=iptrunk_side_ae_members
             or [
                 IptrunkInterfaceBlock.new(
@@ -56,7 +56,7 @@ def iptrunk_subscription_factory(iptrunk_side_subscription_factory, faker, geant
     def subscription_create(
         description=None,
         start_date="2023-05-24T00:00:00+00:00",
-        geant_s_sid=None,
+        gs_id=None,
         iptrunk_description=None,
         iptrunk_type=IptrunkType.LEASED,
         iptrunk_speed=PhysicalPortCapacity.ONE_GIGABIT_PER_SECOND,
@@ -84,7 +84,7 @@ def iptrunk_subscription_factory(iptrunk_side_subscription_factory, faker, geant
             )
 
         description = description or faker.sentence()
-        geant_s_sid = geant_s_sid or faker.geant_sid()
+        gs_id = gs_id or faker.gs_id()
         iptrunk_description = iptrunk_description or faker.sentence()
         iptrunk_isis_metric = iptrunk_isis_metric or faker.pyint()
         iptrunk_ipv4_network = iptrunk_ipv4_network or faker.ipv4_network(max_subnet=31)
@@ -94,7 +94,7 @@ def iptrunk_subscription_factory(iptrunk_side_subscription_factory, faker, geant
         iptrunk_side_b = iptrunk_side_subscription_factory()
         iptrunk_sides = iptrunk_sides or [iptrunk_side_a, iptrunk_side_b]
 
-        iptrunk_subscription.iptrunk.geant_s_sid = geant_s_sid
+        iptrunk_subscription.iptrunk.gs_id = gs_id
         iptrunk_subscription.iptrunk.iptrunk_description = iptrunk_description
         iptrunk_subscription.iptrunk.iptrunk_type = iptrunk_type
         iptrunk_subscription.iptrunk.iptrunk_speed = iptrunk_speed
diff --git a/test/fixtures/l3_core_service_fixtures.py b/test/fixtures/l3_core_service_fixtures.py
index da94298e8ac6b62507ed61147c80233afed84059..21ea6379595e8860d95e35508ec1b6a6e3cd7721 100644
--- a/test/fixtures/l3_core_service_fixtures.py
+++ b/test/fixtures/l3_core_service_fixtures.py
@@ -84,7 +84,7 @@ def service_binding_port_factory(
 ):
     def create_service_binding_port(
         bgp_session_list: list[BGPSession] | None = None,
-        geant_sid: str | None = None,
+        gs_id: str | None = None,
         sbp_type: SBPType = SBPType.L3,
         ipv4_address: str | None = None,
         ipv4_mask: int | None = None,
@@ -108,7 +108,7 @@ def service_binding_port_factory(
             ipv6_address=ipv6_address or faker.ipv6(),
             ipv6_mask=ipv6_mask or faker.ipv6_netmask(),
             custom_firewall_filters=custom_firewall_filters,
-            geant_sid=geant_sid or faker.geant_sid(),
+            gs_id=gs_id or faker.gs_id(),
             bgp_session_list=bgp_session_list
             or [
                 bgp_session_subscription_factory(families=[IPFamily.V4UNICAST]),
diff --git a/test/fixtures/lan_switch_interconnect_fixtures.py b/test/fixtures/lan_switch_interconnect_fixtures.py
index fe0cb87f304b7eb04991bedcb5ad893d743b1483..b98d800d1fa68f5b2e22b8d75c651ceb7dc9adfb 100644
--- a/test/fixtures/lan_switch_interconnect_fixtures.py
+++ b/test/fixtures/lan_switch_interconnect_fixtures.py
@@ -18,7 +18,11 @@ from gso.products.product_types.lan_switch_interconnect import (
 from gso.products.product_types.router import Router
 from gso.products.product_types.switch import Switch
 from gso.services.subscriptions import get_product_id_by_name
-from gso.utils.types.ip_address import AddressSpace, IPv4AddressType, IPv4NetworkType
+from gso.utils.types.virtual_identifiers import (
+    DEFAULT_DCN_MANAGEMENT_VLAN_ID,
+    DEFAULT_SWITCH_MANAGEMENT_VLAN_ID,
+    VLAN_ID,
+)
 
 
 @pytest.fixture()
@@ -31,17 +35,15 @@ def lan_switch_interconnect_subscription_factory(
         status: SubscriptionLifecycle | None = None,
         start_date: str | None = "2024-01-01T10:20:30+01:02",
         lan_switch_interconnect_description: str | None = None,
-        lan_switch_interconnect_ip_network: IPv4NetworkType | None = None,
-        address_space: AddressSpace | None = None,
         minimum_links: int | None = None,
+        switch_management_vlan_id: VLAN_ID | None = None,
+        dcn_management_vlan_id: VLAN_ID | None = None,
         router_side_node: UUIDstr | None = None,
         router_side_ae_iface: str | None = None,
         router_side_ae_members: list[dict[str, str]] | None = None,
-        router_side_ipv4_address: IPv4AddressType | None = None,
         switch_side_switch: UUIDstr | None = None,
         switch_side_ae_iface: str | None = None,
         switch_side_ae_members: list[dict[str, str]] | None = None,
-        switch_side_ipv4_address: IPv4AddressType | None = None,
         *,
         is_imported: bool | None = True,
     ) -> UUIDstr:
@@ -70,24 +72,24 @@ def lan_switch_interconnect_subscription_factory(
         subscription.lan_switch_interconnect.lan_switch_interconnect_description = (
             lan_switch_interconnect_description or faker.sentence()
         )
-        subscription.lan_switch_interconnect.lan_switch_interconnect_ip_network = (
-            lan_switch_interconnect_ip_network or faker.ipv4_network()
-        )
-        subscription.lan_switch_interconnect.address_space = address_space or AddressSpace.PRIVATE
         subscription.lan_switch_interconnect.minimum_links = minimum_links or 1
         subscription.lan_switch_interconnect.router_side = LanSwitchInterconnectRouterSideBlockInactive.new(
             uuid4(),
             node=router_side_node or Router.from_subscription(router_subscription_factory()).router,
             ae_iface=router_side_ae_iface or faker.network_interface(),
             ae_members=router_side_ae_members,
-            ipv4_address=router_side_ipv4_address or faker.ipv4(),
         )
         subscription.lan_switch_interconnect.switch_side = LanSwitchInterconnectSwitchSideBlockInactive.new(
             uuid4(),
             switch=switch_side_switch or Switch.from_subscription(switch_subscription_factory()).switch,
             ae_iface=switch_side_ae_iface or faker.network_interface(),
             ae_members=switch_side_ae_members,
-            ipv4_address=switch_side_ipv4_address or faker.ipv4(),
+        )
+        subscription.lan_switch_interconnect.dcn_management_vlan_id = (
+            dcn_management_vlan_id or DEFAULT_DCN_MANAGEMENT_VLAN_ID
+        )
+        subscription.lan_switch_interconnect.switch_management_vlan_id = (
+            switch_management_vlan_id or DEFAULT_SWITCH_MANAGEMENT_VLAN_ID
         )
 
         subscription = SubscriptionModel.from_other_lifecycle(subscription, SubscriptionLifecycle.ACTIVE)
diff --git a/test/fixtures/layer_2_circuit_fixtures.py b/test/fixtures/layer_2_circuit_fixtures.py
index 28fd88aeb26dfb63d827e1f4741a819633f6af5c..b8772cf44c08c58ed2fdc77a1ec5d21504b668bc 100644
--- a/test/fixtures/layer_2_circuit_fixtures.py
+++ b/test/fixtures/layer_2_circuit_fixtures.py
@@ -38,7 +38,7 @@ def layer_2_circuit_subscription_factory(faker, geant_partner, edge_port_subscri
         vlan_id_side_a: VLAN_ID | None = None,
         layer_2_circuit_side_b_edgeport: UUIDstr | None = None,
         vlan_id_side_b: VLAN_ID | None = None,
-        geant_sid: str | None = None,
+        gs_id: str | None = None,
         *,
         policer_enabled: bool = False,
     ) -> UUIDstr:
@@ -82,7 +82,7 @@ def layer_2_circuit_subscription_factory(faker, geant_partner, edge_port_subscri
                 edge_port=EdgePort.from_subscription(edge_port).edge_port,
                 sbp_type=SBPType.L2,
                 vlan_id=vlan_id,
-                geant_sid=geant_sid or faker.geant_sid(),
+                gs_id=gs_id or faker.gs_id(),
                 is_tagged=layer_2_circuit_type == Layer2CircuitType.TAGGED,
                 custom_firewall_filters=False,
             )
@@ -107,7 +107,7 @@ def layer_2_circuit_subscription_factory(faker, geant_partner, edge_port_subscri
             subscription.layer_2_circuit.bandwidth = None
             subscription.layer_2_circuit.policer_burst_rate = None
         subscription.description = description or (
-            f"{subscription.product.name} - " f"{subscription.layer_2_circuit.virtual_circuit_id}"
+            f"{subscription.product.name} - {subscription.layer_2_circuit.virtual_circuit_id}"
         )
 
         subscription = SubscriptionModel.from_other_lifecycle(subscription, SubscriptionLifecycle.ACTIVE)
diff --git a/test/fixtures/site_fixtures.py b/test/fixtures/site_fixtures.py
index 6fa904183f0d8effe142f2c199f180494cbfd7c2..2467bd89067602cfd8b99270b81a4a350efe93e5 100644
--- a/test/fixtures/site_fixtures.py
+++ b/test/fixtures/site_fixtures.py
@@ -31,6 +31,7 @@ def site_subscription_factory(faker, geant_partner):
         start_date="2023-05-24T00:00:00+00:00",
         *,
         is_imported: bool | None = True,
+        site_contains_optical_equipment: bool | None = True,
     ) -> UUIDstr:
         if partner is None:
             partner = geant_partner
@@ -51,9 +52,10 @@ def site_subscription_factory(faker, geant_partner):
         site_subscription.site.site_latitude = site_latitude or str(faker.latitude())
         site_subscription.site.site_longitude = site_longitude or str(faker.longitude())
         site_subscription.site.site_bgp_community_id = site_bgp_community_id or faker.pyint()
-        site_subscription.site.site_internal_id = site_internal_id or faker.pyint()
+        site_subscription.site.site_internal_id = site_internal_id or faker.pyint(min_value=1, max_value=254)
         site_subscription.site.site_tier = site_tier or SiteTier.TIER1
         site_subscription.site.site_ts_address = site_ts_address or faker.ipv4()
+        site_subscription.site.site_contains_optical_equipment = site_contains_optical_equipment
 
         site_subscription = SubscriptionModel.from_other_lifecycle(site_subscription, SubscriptionLifecycle.ACTIVE)
         site_subscription.description = description or "Site Subscription"
diff --git a/test/fixtures/switch_fixtures.py b/test/fixtures/switch_fixtures.py
index fa7f34d60fd020398455312aed84402afcd7a0c4..c0167a1fbc684f121be38b543fcdc1e9a9bd607d 100644
--- a/test/fixtures/switch_fixtures.py
+++ b/test/fixtures/switch_fixtures.py
@@ -41,7 +41,7 @@ def switch_subscription_factory(faker, geant_partner, site_subscription_factory)
         switch_subscription.switch.ts_port = ts_port or faker.port_number(is_user=True)
         switch_subscription.switch.site = site or Site.from_subscription(site_subscription_factory()).site
         switch_subscription.switch.switch_vendor = switch_vendor or Vendor.JUNIPER
-        switch_subscription.switch.switch_model = switch_model or SwitchModel.EX3400
+        switch_subscription.switch.switch_model = switch_model or SwitchModel.EX3400_24T
 
         switch_subscription = SubscriptionModel.from_other_lifecycle(switch_subscription, SubscriptionLifecycle.ACTIVE)
         switch_subscription.insync = True
diff --git a/test/services/test_lso_client.py b/test/services/test_lso_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..2f52072a723c8656f79a2ea5abb0ee6127920cc7
--- /dev/null
+++ b/test/services/test_lso_client.py
@@ -0,0 +1,28 @@
+from unittest.mock import patch
+
+from gso.services.lso_client import _execute_playbook
+
+
+@patch("gso.services.lso_client.requests.post")
+def test_replace_unicode_in_lso_call_success(mock_post):
+    extra_vars = {
+        "deployment_description": "I am going to deploy the best GÉANT service EVER!!",
+        "email": "goat@géant.org",
+        "translations": {"ja": "ジェアントのスゴイなサービスをデプロイする"},
+    }
+
+    expected_parameters = {
+        "playbook_name": "playbook.yaml",
+        "inventory": {},
+        "callback": "http://gso-api:9000/api/callback_route",
+        "extra_vars": {
+            "deployment_description": "I am going to deploy the best GEANT service EVER!!",
+            "email": "goat@geant.org",
+            "translations": {"ja": "zieantonosugoinasa-bisuwodepuroisuru"},
+        },
+    }
+
+    execute_playbook = _execute_playbook.__wrapped__
+    execute_playbook("playbook.yaml", "/api/callback_route", {}, extra_vars)
+
+    mock_post.assert_called_once_with("https://localhost:44444/api/playbook", json=expected_parameters, timeout=10)
diff --git a/test/utils/test_helpers.py b/test/utils/test_helpers.py
index e91133780843a15e5b1cba7466f22dbb0700fe99..6b62f2bb4554c23fd80dd27ed793d6eeac0577f8 100644
--- a/test/utils/test_helpers.py
+++ b/test/utils/test_helpers.py
@@ -6,9 +6,12 @@ from orchestrator.types import SubscriptionLifecycle
 from gso.products.product_blocks.iptrunk import IptrunkInterfaceBlock
 from gso.products.product_blocks.router import RouterRole
 from gso.products.product_types.router import Router
+from gso.products.product_types.site import Site
 from gso.utils.helpers import (
     available_interfaces_choices_including_current_members,
     generate_inventory_for_routers,
+    generate_lan_switch_interconnect_subnet_v4,
+    generate_lan_switch_interconnect_subnet_v6,
 )
 from gso.utils.shared_enums import Vendor
 from gso.utils.types.tt_number import validate_tt_number
@@ -139,3 +142,25 @@ def test_generate_inventory_for_active_routers_with_excluded_router(router_subsc
     excluded_routers = [Router.from_subscription(router).router.router_fqdn]
     inventory = generate_inventory_for_routers(RouterRole.P, exclude_routers=excluded_routers)
     assert len(inventory["all"]["hosts"]) == 5  # 6 P routers, the last one is excluded, so 5 P routers are left.
+
+
+@pytest.mark.parametrize("execution_count", range(10))
+def test_generate_lan_switch_interconnect_subnet_v4(execution_count, site_subscription_factory):
+    """Test generating a new subnet for a LAN Switch Interconnect.
+
+    We need to ensure that the third octet of the new subnet is set correctly from the Site internal ID.
+    """
+    site = Site.from_subscription(site_subscription_factory())
+    assert (
+        str(generate_lan_switch_interconnect_subnet_v4(site.site.site_internal_id))
+        == f"10.2.{site.site.site_internal_id}.0/24"
+    )
+
+
+@pytest.mark.parametrize("execution_count", range(10))
+def test_generate_lan_switch_interconnect_subnet_v6(execution_count, site_subscription_factory):
+    site = Site.from_subscription(site_subscription_factory())
+    assert (
+        str(generate_lan_switch_interconnect_subnet_v6(site.site.site_internal_id))
+        == f"beef:cafe:0:{hex(site.site.site_internal_id).split("x")[-1]}::/64"
+    )
diff --git a/test/workflows/edge_port/test_create_edge_port.py b/test/workflows/edge_port/test_create_edge_port.py
index 8066c5e0217668853f7243c1d1cdfe16b4441228..107dab95f2a328b4d1c7746ba0e605a65e255906 100644
--- a/test/workflows/edge_port/test_create_edge_port.py
+++ b/test/workflows/edge_port/test_create_edge_port.py
@@ -50,7 +50,6 @@ def input_form_wizard_data(request, router_subscription_factory, partner_factory
         "node": router_subscription_factory(vendor=Vendor.NOKIA),
         "partner": partner_factory(name="GAAR", email=faker.email())["partner_id"],
         "service_type": EdgePortType.PUBLIC,
-        "geant_ga_id": faker.geant_gid(),
         "enable_lacp": True,
         "speed": PhysicalPortCapacity.HUNDRED_GIGABIT_PER_SECOND,
         "encapsulation": EncapsulationType.DOT1Q,
@@ -101,9 +100,9 @@ def test_successful_edge_port_creation(
     subscription = EdgePort.from_subscription(subscription_id)
 
     assert subscription.status == "active"
-    ga_id = input_form_wizard_data[0]["geant_ga_id"]
     router_fqdn = Router.from_subscription(input_form_wizard_data[0]["node"]).router.router_fqdn
-    assert subscription.description == f"Edge Port lag-21 on {router_fqdn}, GAAR, {ga_id}"
+    assert subscription.edge_port.ga_id is not None
+    assert subscription.description == f"Edge Port lag-21 on {router_fqdn}, GAAR, {subscription.edge_port.ga_id}"
     assert len(subscription.edge_port.edge_port_ae_members) == 2
     assert mock_execute_playbook.call_count == 2
 
diff --git a/test/workflows/edge_port/test_create_imported_edge_port.py b/test/workflows/edge_port/test_create_imported_edge_port.py
index c5590fe4f99c03c38c7a62b7129aa91ef8caef9a..ea5b024e5ef9bdc1de35214e5c487a22979f7b6e 100644
--- a/test/workflows/edge_port/test_create_imported_edge_port.py
+++ b/test/workflows/edge_port/test_create_imported_edge_port.py
@@ -17,7 +17,7 @@ def imported_edge_port_creation_input_form_data(router_subscription_factory, par
         "encapsulation": EncapsulationType.DOT1Q,
         "name": "lag34",
         "minimum_links": 2,
-        "geant_ga_id": faker.geant_gid(),
+        "ga_id": faker.imported_ga_id(),
         "mac_address": faker.mac_address(),
         "partner": partner_factory()["name"],
         "enable_lacp": True,
diff --git a/test/workflows/edge_port/test_modify_edge_port.py b/test/workflows/edge_port/test_modify_edge_port.py
index a7c824283f031c4dcb14cda53a7c8dfbb35527bb..0f7ed1401db0e4e4fecac754ab938b5f1b246748 100644
--- a/test/workflows/edge_port/test_modify_edge_port.py
+++ b/test/workflows/edge_port/test_modify_edge_port.py
@@ -1,6 +1,7 @@
 from unittest.mock import patch
 
 import pytest
+from pydantic_forms.exceptions import FormValidationError
 
 from gso.products.product_types.edge_port import EdgePort
 from gso.utils.types.interfaces import PhysicalPortCapacity
@@ -21,7 +22,7 @@ def input_form_wizard_data(request, faker, edge_port_subscription_factory, partn
         {"subscription_id": subscription_id},
         {
             "tt_number": faker.tt_number(),
-            "geant_ga_id": faker.geant_gid(),
+            "ga_id": faker.ga_id(),
             "member_speed": PhysicalPortCapacity.FOUR_HUNDRED_GIGABIT_PER_SECOND,
             "number_of_members": 1,
         },
@@ -37,6 +38,22 @@ def input_form_wizard_data(request, faker, edge_port_subscription_factory, partn
     ]
 
 
+@pytest.mark.workflow()
+@pytest.mark.parametrize("invalid_ga_id", ["GS-11111", "GA-1234", "GA_12345", "GA-100000"])
+def test_modify_edge_port_with_invalid_ga_id(
+    input_form_wizard_data, faker, invalid_ga_id, iptrunk_side_subscription_factory, iptrunk_subscription_factory
+):
+    input_data = input_form_wizard_data
+    input_data[1]["ga_id"] = invalid_ga_id
+    iptrunk_subscription_factory(
+        iptrunk_sides=[iptrunk_side_subscription_factory(ga_id="GA-11111"), iptrunk_side_subscription_factory()]
+    )
+
+    #  Run workflow
+    with pytest.raises(FormValidationError):
+        run_workflow("modify_edge_port", input_data)
+
+
 @pytest.mark.workflow()
 @patch("gso.services.lso_client._send_request")
 @patch("gso.services.netbox_client.NetboxClient.get_available_interfaces")
@@ -89,7 +106,7 @@ def test_modify_edge_port_with_changing_capacity(
     assert mocked_attach_interface_to_lag.call_count == 1
     assert mocked_free_interface.call_count == 2
     assert mocked_detach_interfaces_from_lag.call_count == 1
-    assert subscription.edge_port.geant_ga_id == input_form_wizard_data[1]["geant_ga_id"]
+    assert subscription.edge_port.ga_id == input_form_wizard_data[1]["ga_id"]
     assert len(subscription.edge_port.edge_port_ae_members) == 1
 
 
@@ -100,7 +117,7 @@ def input_form_wizard_without_changing_capacity(request, faker, edge_port_subscr
 
     return [
         {"subscription_id": subscription_id},
-        {"tt_number": faker.tt_number(), "geant_ga_id": faker.geant_gid()},
+        {"tt_number": faker.tt_number(), "ga_id": faker.ga_id()},
         {
             "description": faker.sentence(),
             "ae_members": [
@@ -163,6 +180,6 @@ def test_modify_edge_port_without_changing_capacity(
     assert mocked_free_interface.call_count == 0
     assert mocked_detach_interfaces_from_lag.call_count == 0
 
-    assert subscription.edge_port.geant_ga_id == input_form_wizard_without_changing_capacity[1]["geant_ga_id"]
+    assert subscription.edge_port.ga_id == input_form_wizard_without_changing_capacity[1]["ga_id"]
     assert len(subscription.edge_port.edge_port_ae_members) == 2
     assert subscription.edge_port.edge_port_description == input_form_wizard_without_changing_capacity[2]["description"]
diff --git a/test/workflows/iptrunk/test_create_imported_iptrunk.py b/test/workflows/iptrunk/test_create_imported_iptrunk.py
index 25d68d8f9f67906b5251b1312927d2c3203ddbad..deaa2ddd519b8f699633e35d6713414f4e3db8d1 100644
--- a/test/workflows/iptrunk/test_create_imported_iptrunk.py
+++ b/test/workflows/iptrunk/test_create_imported_iptrunk.py
@@ -16,7 +16,7 @@ from test.workflows import (
 def workflow_input_data(faker, router_subscription_factory):
     return {
         "partner": "GEANT",
-        "geant_s_sid": faker.geant_sid(),
+        "gs_id": faker.imported_gs_id(),
         "iptrunk_description": faker.sentence(),
         "iptrunk_type": IptrunkType.DARK_FIBER,
         "iptrunk_speed": PhysicalPortCapacity.FOUR_HUNDRED_GIGABIT_PER_SECOND,
@@ -24,13 +24,13 @@ def workflow_input_data(faker, router_subscription_factory):
         "iptrunk_isis_metric": 10000,
         "side_a_node_id": router_subscription_factory(),
         "side_a_ae_iface": faker.network_interface(),
-        "side_a_ae_geant_a_sid": faker.geant_sid(),
+        "side_a_ga_id": faker.imported_ga_id(),
         "side_a_ae_members": [
             {"interface_name": faker.network_interface(), "interface_description": faker.sentence()} for _ in range(3)
         ],
         "side_b_node_id": router_subscription_factory(),
         "side_b_ae_iface": faker.network_interface(),
-        "side_b_ae_geant_a_sid": faker.geant_sid(),
+        "side_b_ga_id": faker.imported_ga_id(),
         "side_b_ae_members": [
             {"interface_name": faker.network_interface(), "interface_description": faker.sentence()} for _ in range(3)
         ],
diff --git a/test/workflows/iptrunk/test_create_iptrunk.py b/test/workflows/iptrunk/test_create_iptrunk.py
index cac63729ec0507232f904074c48bb7c8b5f203a1..d0f9cfa539b3b52600dff506931c3cd0699250ce 100644
--- a/test/workflows/iptrunk/test_create_iptrunk.py
+++ b/test/workflows/iptrunk/test_create_iptrunk.py
@@ -65,7 +65,6 @@ def input_form_wizard_data(request, router_subscription_factory, faker):
 
     create_ip_trunk_step = {
         "tt_number": faker.tt_number(),
-        "geant_s_sid": faker.geant_sid(),
         "iptrunk_type": IptrunkType.DARK_FIBER,
         "iptrunk_description": faker.sentence(),
         "iptrunk_speed": PhysicalPortCapacity.HUNDRED_GIGABIT_PER_SECOND,
@@ -75,7 +74,6 @@ def input_form_wizard_data(request, router_subscription_factory, faker):
     create_ip_trunk_side_a_router_name = {"side_a_node_id": router_side_a}
     create_ip_trunk_side_a_step = {
         "side_a_ae_iface": "lag-1",
-        "side_a_ae_geant_a_sid": None,
         "side_a_ae_members": [
             {
                 "interface_name": f"Interface{interface}",
@@ -87,7 +85,6 @@ def input_form_wizard_data(request, router_subscription_factory, faker):
     create_ip_trunk_side_b_router_name = {"side_b_node_id": router_side_b}
     create_ip_trunk_side_b_step = {
         "side_b_ae_iface": "lag-4",
-        "side_b_ae_geant_a_sid": faker.geant_sid(),
         "side_b_ae_members": side_b_members,
     }
     summary_view_step = {}
@@ -153,9 +150,8 @@ def test_successful_iptrunk_creation_with_standard_lso_result(
         subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_site.site_name,
     ])
     assert subscription.status == "provisioning"
-    assert subscription.description == (
-        f"IP trunk {sorted_sides[0]} {sorted_sides[1]}, geant_s_sid:{input_form_wizard_data[0]["geant_s_sid"]}"
-    )
+    assert subscription.iptrunk.gs_id is not None
+    assert subscription.description == f"IP trunk {sorted_sides[0]} {sorted_sides[1]}, {subscription.iptrunk.gs_id}"
 
     assert mock_execute_playbook.call_count == 6
     #  We search for 6 hosts in total, 2 in a /31 and 4 in a /126
diff --git a/test/workflows/iptrunk/test_modify_trunk_interface.py b/test/workflows/iptrunk/test_modify_trunk_interface.py
index 2dcb23e2b00a9b063144e827f434129f5d9411e7..d8a96588a3bc6182fa498b2b50a8e849c646b483 100644
--- a/test/workflows/iptrunk/test_modify_trunk_interface.py
+++ b/test/workflows/iptrunk/test_modify_trunk_interface.py
@@ -1,6 +1,7 @@
 from unittest.mock import patch
 
 import pytest
+from pydantic_forms.exceptions import FormValidationError
 
 from gso.products.product_blocks.iptrunk import IptrunkType
 from gso.products.product_types.iptrunk import Iptrunk
@@ -52,21 +53,21 @@ def input_form_iptrunk_data(
 
     product_id = iptrunk_subscription_factory(iptrunk_sides=[side_a_node, side_b_node])
 
-    new_sid = faker.geant_sid()
+    new_sid = faker.gs_id()
     new_description = faker.sentence()
     new_type = IptrunkType.LEASED
     new_speed = PhysicalPortCapacity.FOUR_HUNDRED_GIGABIT_PER_SECOND
     new_link_count = 2
 
-    new_side_a_sid = faker.geant_sid()
+    new_side_a_gid = faker.ga_id()
 
-    new_side_b_sid = faker.geant_sid()
+    new_side_b_gid = faker.ga_id()
 
     return [
         {"subscription_id": product_id},
         {
             "tt_number": faker.tt_number(),
-            "geant_s_sid": new_sid,
+            "gs_id": new_sid,
             "iptrunk_description": new_description,
             "iptrunk_type": new_type,
             "iptrunk_speed": new_speed,
@@ -74,11 +75,11 @@ def input_form_iptrunk_data(
         },
         {},
         {
-            "side_a_ae_geant_a_sid": new_side_a_sid,
+            "side_a_ga_id": new_side_a_gid,
             "side_a_ae_members": new_side_a_ae_members,
         },
         {
-            "side_b_ae_geant_a_sid": new_side_b_sid,
+            "side_b_ga_id": new_side_b_gid,
             "side_b_ae_members": new_side_b_ae_members,
         },
     ]
@@ -135,10 +136,10 @@ def test_iptrunk_modify_trunk_interface_success(
     assert subscription.status == "active"
     assert mock_provision_ip_trunk.call_count == lso_interaction_count
     # Assert all Netbox calls have been made
-    new_sid = input_form_iptrunk_data[1]["geant_s_sid"]
-    new_side_a_sid = input_form_iptrunk_data[3]["side_a_ae_geant_a_sid"]
+    new_sid = input_form_iptrunk_data[1]["gs_id"]
+    new_side_a_gid = input_form_iptrunk_data[3]["side_a_ga_id"]
     new_side_a_ae_members = input_form_iptrunk_data[3]["side_a_ae_members"]
-    new_side_b_sid = input_form_iptrunk_data[4]["side_b_ae_geant_a_sid"]
+    new_side_b_gid = input_form_iptrunk_data[4]["side_b_ga_id"]
     new_side_b_ae_members = input_form_iptrunk_data[4]["side_b_ae_members"]
 
     # Only Nokia interfaces are checked
@@ -164,13 +165,13 @@ def test_iptrunk_modify_trunk_interface_success(
         subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_site.site_name,
         subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_site.site_name,
     ])
-    assert subscription.description == f"IP trunk {side_names[0]} {side_names[1]}, geant_s_sid:{new_sid}"
-    assert subscription.iptrunk.geant_s_sid == input_form_iptrunk_data[1]["geant_s_sid"]
+    assert subscription.description == f"IP trunk {side_names[0]} {side_names[1]}, {new_sid}"
+    assert subscription.iptrunk.gs_id == input_form_iptrunk_data[1]["gs_id"]
     assert subscription.iptrunk.iptrunk_description == input_form_iptrunk_data[1]["iptrunk_description"]
     assert subscription.iptrunk.iptrunk_type == input_form_iptrunk_data[1]["iptrunk_type"]
     assert subscription.iptrunk.iptrunk_speed == input_form_iptrunk_data[1]["iptrunk_speed"]
     assert subscription.iptrunk.iptrunk_minimum_links == input_form_iptrunk_data[1]["iptrunk_number_of_members"] - 1
-    assert subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_geant_a_sid == new_side_a_sid
+    assert subscription.iptrunk.iptrunk_sides[0].ga_id == new_side_a_gid
 
     def _find_interface_by_name(interfaces: LAGMemberList, name: str):
         for interface in interfaces:
@@ -185,10 +186,26 @@ def test_iptrunk_modify_trunk_interface_success(
             == _find_interface_by_name(new_side_a_ae_members, member.interface_name).interface_description
         )
 
-    assert subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_geant_a_sid == new_side_b_sid
+    assert subscription.iptrunk.iptrunk_sides[1].ga_id == new_side_b_gid
 
     for member in subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members:
         assert (
             member.interface_description
             == _find_interface_by_name(new_side_b_ae_members, member.interface_name).interface_description
         )
+
+
+@pytest.mark.workflow()
+@pytest.mark.parametrize("invalid_ga_id", ["GA-11111", "GS-1234", "GS_12345", "GS-100000"])
+def test_modify_iptrunk_interface_with_invalid_ga_id(
+    input_form_iptrunk_data, faker, invalid_ga_id, iptrunk_side_subscription_factory, iptrunk_subscription_factory
+):
+    input_data = input_form_iptrunk_data
+    input_data[3]["side_a_ga_id"] = invalid_ga_id
+    iptrunk_subscription_factory(
+        iptrunk_sides=[iptrunk_side_subscription_factory(ga_id="GA-11111"), iptrunk_side_subscription_factory()]
+    )
+
+    #  Run workflow
+    with pytest.raises(FormValidationError):
+        run_workflow("modify_edge_port", input_data)
diff --git a/test/workflows/l2_circuit/test_create_imported_layer_2_circuit.py b/test/workflows/l2_circuit/test_create_imported_layer_2_circuit.py
index 0e4e857ef32e9d002cd175176fa1ba7a463ce318..3d0e78709dd4b708af3e46cdd19110611edbbfc4 100644
--- a/test/workflows/l2_circuit/test_create_imported_layer_2_circuit.py
+++ b/test/workflows/l2_circuit/test_create_imported_layer_2_circuit.py
@@ -28,7 +28,7 @@ def test_create_imported_layer_2_circuit_success(
             "vc_id": generate_unique_vc_id(),
             "policer_bandwidth": faker.bandwidth() if policer_enabled else None,
             "policer_burst_rate": faker.bandwidth() if policer_enabled else None,
-            "geant_sid": faker.geant_sid(),
+            "gs_id": faker.imported_gs_id(),
             "layer_2_circuit_side_a": {"edge_port": edge_port_a, "vlan_id": faker.vlan_id()},
             "layer_2_circuit_side_b": {"edge_port": edge_port_b, "vlan_id": faker.vlan_id()},
         }
@@ -43,9 +43,7 @@ def test_create_imported_layer_2_circuit_success(
     assert subscription.layer_2_circuit.virtual_circuit_id == creation_form_input_data[0]["vc_id"]
     assert len(subscription.layer_2_circuit.layer_2_circuit_sides) == 2
     assert subscription.layer_2_circuit.layer_2_circuit_sides[0].sbp.is_tagged is True
-    assert (
-        subscription.layer_2_circuit.layer_2_circuit_sides[0].sbp.geant_sid == creation_form_input_data[0]["geant_sid"]
-    )
+    assert subscription.layer_2_circuit.layer_2_circuit_sides[0].sbp.gs_id == creation_form_input_data[0]["gs_id"]
     assert (
         str(subscription.layer_2_circuit.layer_2_circuit_sides[0].sbp.edge_port.owner_subscription_id)
         == creation_form_input_data[0]["layer_2_circuit_side_a"]["edge_port"]
diff --git a/test/workflows/l2_circuit/test_create_layer_2_circuit.py b/test/workflows/l2_circuit/test_create_layer_2_circuit.py
index c0d1b2df6aae5014f24e0fe4531a4d52a5007411..c14ab1213a41f24414b56046fbeb4a8afef43110 100644
--- a/test/workflows/l2_circuit/test_create_layer_2_circuit.py
+++ b/test/workflows/l2_circuit/test_create_layer_2_circuit.py
@@ -28,7 +28,6 @@ def layer_2_circuit_input(faker, partner_factory, edge_port_subscription_factory
             "vlan_range_upper_bound": faker.vlan_id(),
             "policer_bandwidth": faker.bandwidth() if policer_enabled else None,
             "policer_burst_rate": faker.bandwidth() if policer_enabled else None,
-            "geant_sid": faker.geant_sid(),
             "layer_2_circuit_side_a": {"edge_port": edge_port_a, "vlan_id": faker.vlan_id()},
             "layer_2_circuit_side_b": {"edge_port": edge_port_b, "vlan_id": faker.vlan_id()},
         },
@@ -56,13 +55,15 @@ def test_create_layer_2_circuit_success(
         == layer_2_circuit_input[2]["layer_2_circuit_side_a"]["edge_port"]
     )
     assert subscription.layer_2_circuit.layer_2_circuit_sides[0].sbp.is_tagged is True
-    assert subscription.layer_2_circuit.layer_2_circuit_sides[0].sbp.geant_sid == layer_2_circuit_input[2]["geant_sid"]
+    assert (
+        subscription.layer_2_circuit.layer_2_circuit_sides[0].sbp.gs_id
+        == subscription.layer_2_circuit.layer_2_circuit_sides[1].sbp.gs_id
+    )
     assert (
         str(subscription.layer_2_circuit.layer_2_circuit_sides[1].sbp.edge_port.owner_subscription_id)
         == layer_2_circuit_input[2]["layer_2_circuit_side_b"]["edge_port"]
     )
     assert subscription.layer_2_circuit.layer_2_circuit_sides[1].sbp.is_tagged is True
-    assert subscription.layer_2_circuit.layer_2_circuit_sides[1].sbp.geant_sid == layer_2_circuit_input[2]["geant_sid"]
     assert subscription.layer_2_circuit.layer_2_circuit_type == Layer2CircuitType.TAGGED
     assert subscription.layer_2_circuit.vlan_range_lower_bound == layer_2_circuit_input[2]["vlan_range_lower_bound"]
     assert subscription.layer_2_circuit.vlan_range_upper_bound == layer_2_circuit_input[2]["vlan_range_upper_bound"]
diff --git a/test/workflows/l3_core_service/test_create_imported_l3_core_service.py b/test/workflows/l3_core_service/test_create_imported_l3_core_service.py
index 626c62378746d7b3625f490325cc7d362a6b4de1..48ac4b54d40fa6be2e76a3fea17d98c44bda8d33 100644
--- a/test/workflows/l3_core_service/test_create_imported_l3_core_service.py
+++ b/test/workflows/l3_core_service/test_create_imported_l3_core_service.py
@@ -27,7 +27,7 @@ def test_create_imported_l3_core_service_success(
             {
                 "edge_port": edge_port_subscription_factory(),
                 "ap_type": "PRIMARY",
-                "geant_sid": faker.geant_sid(),
+                "gs_id": faker.imported_gs_id(),
                 "sbp_type": SBPType.L3,
                 "is_tagged": faker.boolean(),
                 "vlan_id": faker.vlan_id(),
diff --git a/test/workflows/l3_core_service/test_create_l3_core_service.py b/test/workflows/l3_core_service/test_create_l3_core_service.py
index 149fdf202357a583d52d4010eb7007eb7582413d..c26e649443f01d44d024062038d5d7192c54fa3a 100644
--- a/test/workflows/l3_core_service/test_create_l3_core_service.py
+++ b/test/workflows/l3_core_service/test_create_l3_core_service.py
@@ -49,7 +49,6 @@ def test_create_l3_core_service_success(
         {"tt_number": faker.tt_number(), "partner": partner["partner_id"]},
         {"edge_port": {"edge_port": edge_port_a, "ap_type": APType.PRIMARY}},
         {
-            "geant_sid": faker.geant_sid(),
             "is_tagged": faker.boolean(),
             "vlan_id": faker.vlan_id(),
             "ipv4_address": faker.ipv4(),
diff --git a/test/workflows/l3_core_service/test_modify_l3_core_service.py b/test/workflows/l3_core_service/test_modify_l3_core_service.py
index 67ff888097afbed5db3403a6ca926cdd00dda54e..dc7f3a510f92ed1d6361f0c1799528dbbe63c523 100644
--- a/test/workflows/l3_core_service/test_modify_l3_core_service.py
+++ b/test/workflows/l3_core_service/test_modify_l3_core_service.py
@@ -84,7 +84,7 @@ def test_modify_l3_core_service_add_new_edge_port_success(
         {},  # The existing SBPs are unchanged
         {},
         {  # Adding configuration for the new SBP
-            "geant_sid": faker.geant_sid(),
+            "gs_id": faker.gs_id(),
             "vlan_id": faker.vlan_id(),
             "ipv4_address": faker.ipv4(),
             "ipv6_address": faker.ipv6(),
@@ -115,7 +115,7 @@ def test_modify_l3_core_service_add_new_edge_port_success(
 def sbp_input_form_data(faker):
     def _generate_form_data():
         return {
-            "geant_sid": faker.geant_sid(),
+            "gs_id": faker.gs_id(),
             "is_tagged": True,
             "vlan_id": faker.vlan_id(),
             "ipv4_address": faker.ipv4(),
@@ -198,7 +198,7 @@ def test_modify_l3_core_service_modify_edge_port_success(
     assert len(subscription.l3_core_service.ap_list) == 2
 
     for i in range(2):
-        assert subscription.l3_core_service.ap_list[i].sbp.geant_sid == new_sbp_data[i]["geant_sid"]
+        assert subscription.l3_core_service.ap_list[i].sbp.gs_id == new_sbp_data[i]["gs_id"]
         assert subscription.l3_core_service.ap_list[i].sbp.is_tagged == new_sbp_data[i]["is_tagged"]
         assert subscription.l3_core_service.ap_list[i].sbp.vlan_id == new_sbp_data[i]["vlan_id"]
         assert str(subscription.l3_core_service.ap_list[i].sbp.ipv4_address) == new_sbp_data[i]["ipv4_address"]
diff --git a/test/workflows/lan_switch_interconnect/test_create_imported_lan_switch_interconnect.py b/test/workflows/lan_switch_interconnect/test_create_imported_lan_switch_interconnect.py
index 69349ae02c7d58dbbdc670aad70b855edb5f7a11..8c3f9c4c6d363d8c96377438ebd9474c6aafd122 100644
--- a/test/workflows/lan_switch_interconnect/test_create_imported_lan_switch_interconnect.py
+++ b/test/workflows/lan_switch_interconnect/test_create_imported_lan_switch_interconnect.py
@@ -3,7 +3,7 @@ from orchestrator.types import SubscriptionLifecycle
 
 from gso.products import ProductName
 from gso.products.product_types.lan_switch_interconnect import ImportedLanSwitchInterconnect
-from gso.utils.types.ip_address import AddressSpace
+from gso.utils.types.virtual_identifiers import DEFAULT_DCN_MANAGEMENT_VLAN_ID, DEFAULT_SWITCH_MANAGEMENT_VLAN_ID
 from test.workflows import (
     assert_complete,
     extract_state,
@@ -15,20 +15,16 @@ from test.workflows import (
 def workflow_input_data(faker, router_subscription_factory, switch_subscription_factory):
     return {
         "lan_switch_interconnect_description": faker.sentence(),
-        "lan_switch_interconnect_ip_network": faker.ipv4_network(),
-        "address_space": AddressSpace.PUBLIC,
         "minimum_links": 1,
         "router_side": {
             "node": router_subscription_factory(),
             "ae_iface": faker.nokia_lag_interface_name(),
             "ae_members": faker.link_members_nokia(),
-            "ipv4_address": faker.ipv4(),
         },
         "switch_side": {
             "switch": switch_subscription_factory(),
             "ae_iface": faker.juniper_ae_interface_name(),
             "ae_members": faker.link_members_juniper(),
-            "ipv4_address": faker.ipv4(),
         },
     }
 
@@ -42,3 +38,24 @@ def test_create_imported_lan_switch_interconnect_success(workflow_input_data):
     assert_complete(result)
     assert subscription.product.name == ProductName.IMPORTED_LAN_SWITCH_INTERCONNECT
     assert subscription.status == SubscriptionLifecycle.ACTIVE
+    assert subscription.lan_switch_interconnect.dcn_management_vlan_id == DEFAULT_DCN_MANAGEMENT_VLAN_ID
+    assert subscription.lan_switch_interconnect.switch_management_vlan_id == DEFAULT_SWITCH_MANAGEMENT_VLAN_ID
+
+
+@pytest.mark.workflow()
+def test_create_imported_lan_switch_interconnect_custom_vlan_ids(faker, workflow_input_data):
+    custom_switch_vlan_id = faker.vlan_id()
+    custom_dcn_vlan_id = faker.vlan_id()
+    workflow_input_data.update({
+        "switch_management_vlan_id": custom_switch_vlan_id,
+        "dcn_management_vlan_id": custom_dcn_vlan_id,
+    })
+    result, _, _ = run_workflow("create_imported_lan_switch_interconnect", [workflow_input_data])
+    state = extract_state(result)
+    subscription = ImportedLanSwitchInterconnect.from_subscription(state["subscription_id"])
+
+    assert_complete(result)
+    assert subscription.product.name == ProductName.IMPORTED_LAN_SWITCH_INTERCONNECT
+    assert subscription.status == SubscriptionLifecycle.ACTIVE
+    assert subscription.lan_switch_interconnect.dcn_management_vlan_id == custom_dcn_vlan_id
+    assert subscription.lan_switch_interconnect.switch_management_vlan_id == custom_switch_vlan_id
diff --git a/test/workflows/lan_switch_interconnect/test_create_lan_switch_interconnect.py b/test/workflows/lan_switch_interconnect/test_create_lan_switch_interconnect.py
index 2c6657d260a88b8871dcc843a8fb1e7a636685f4..a0c338041649ae37ad12835855e65640921ed61f 100644
--- a/test/workflows/lan_switch_interconnect/test_create_lan_switch_interconnect.py
+++ b/test/workflows/lan_switch_interconnect/test_create_lan_switch_interconnect.py
@@ -6,7 +6,7 @@ from orchestrator.types import SubscriptionLifecycle
 from gso.products import ProductName
 from gso.products.product_types.lan_switch_interconnect import LanSwitchInterconnect
 from gso.services.subscriptions import get_product_id_by_name
-from gso.utils.types.ip_address import AddressSpace
+from gso.utils.types.virtual_identifiers import DEFAULT_DCN_MANAGEMENT_VLAN_ID
 from test.services.conftest import MockedNetboxClient
 from test.workflows import assert_complete, extract_state, run_workflow
 
@@ -27,20 +27,22 @@ def _netbox_client_mock():
 
 
 @pytest.fixture()
-def input_form_data(faker, router_subscription_factory, switch_subscription_factory):
-    def _generate_form_data(address_space: AddressSpace):
+def input_form_data(faker, router_subscription_factory, switch_subscription_factory, site_subscription_factory):
+    def _input_form_data(site_contains_optical_equipment):
         return [
             {
                 "product": get_product_id_by_name(ProductName.LAN_SWITCH_INTERCONNECT),
             },
             {
                 "tt_number": faker.tt_number(),
-                "router_side": router_subscription_factory(),
+                "router_side": router_subscription_factory(
+                    router_site=site_subscription_factory(
+                        site_contains_optical_equipment=site_contains_optical_equipment
+                    )
+                ),
                 "switch_side": switch_subscription_factory(),
-                "address_space": address_space,
                 "description": faker.sentence(),
                 "minimum_link_count": 2,
-                "vlan_id": 111,  # VLAN ID for new interconnections is always 111
             },
             {
                 "router_side_iface": "lag-1",
@@ -53,22 +55,32 @@ def input_form_data(faker, router_subscription_factory, switch_subscription_fact
             {},
         ]
 
-    return _generate_form_data
+    return _input_form_data
 
 
-@pytest.mark.parametrize("address_space", [AddressSpace.PRIVATE, AddressSpace.PUBLIC])
 @pytest.mark.workflow()
+@pytest.mark.parametrize("site_contains_optical_equipment", [True, False])
+@patch("gso.workflows.lan_switch_interconnect.create_lan_switch_interconnect.create_v6_network_by_ip")
+@patch("gso.workflows.lan_switch_interconnect.create_lan_switch_interconnect.create_v4_network_by_ip")
+@patch("gso.workflows.lan_switch_interconnect.create_lan_switch_interconnect.create_host_by_ip")
 def test_create_lan_switch_interconnect_success(
-    address_space,
+    mock_create_host,
+    mock_create_v4_network,
+    mock_create_v6_network,
+    site_contains_optical_equipment,
     input_form_data,
     _netbox_client_mock,  # noqa: PT019
 ):
-    initial_data = input_form_data(address_space)
-
-    result, _, _ = run_workflow("create_lan_switch_interconnect", initial_data)
+    result, _, _ = run_workflow("create_lan_switch_interconnect", input_form_data(site_contains_optical_equipment))
 
     assert_complete(result)
     state = extract_state(result)
     subscription_id = state["subscription_id"]
     subscription = LanSwitchInterconnect.from_subscription(subscription_id)
     assert subscription.status == SubscriptionLifecycle.ACTIVE
+    assert mock_create_v4_network.call_count == 1
+    assert mock_create_v6_network.call_count == 1
+    assert mock_create_host.call_count == 4
+    assert subscription.lan_switch_interconnect.dcn_management_vlan_id == (
+        DEFAULT_DCN_MANAGEMENT_VLAN_ID if site_contains_optical_equipment else None
+    )
diff --git a/test/workflows/lan_switch_interconnect/test_terminate_lan_switch_interconnect.py b/test/workflows/lan_switch_interconnect/test_terminate_lan_switch_interconnect.py
index 6d5ae5383dd4559662f293535a3b3253d7123033..9b69c628e440e90c76460656addf7c447abe4793 100644
--- a/test/workflows/lan_switch_interconnect/test_terminate_lan_switch_interconnect.py
+++ b/test/workflows/lan_switch_interconnect/test_terminate_lan_switch_interconnect.py
@@ -1,3 +1,5 @@
+from unittest.mock import patch
+
 import pytest
 
 from gso.products.product_types.lan_switch_interconnect import LanSwitchInterconnect
@@ -5,7 +7,11 @@ from test.workflows import assert_complete, extract_state, run_workflow
 
 
 @pytest.mark.workflow()
-def test_terminate_lan_switch_interconnect(lan_switch_interconnect_subscription_factory, faker):
+@patch("gso.services.infoblox.find_host_by_fqdn")
+@patch("gso.services.infoblox.delete_network")
+def test_terminate_lan_switch_interconnect(
+    mock_delete_network, mock_find_host, lan_switch_interconnect_subscription_factory, faker
+):
     subscription_id = lan_switch_interconnect_subscription_factory()
     initial_lan_switch_interconnect_data = [{"subscription_id": subscription_id}, {"tt_number": faker.tt_number()}]
     result, _, _ = run_workflow("terminate_lan_switch_interconnect", initial_lan_switch_interconnect_data)
@@ -15,3 +21,5 @@ def test_terminate_lan_switch_interconnect(lan_switch_interconnect_subscription_
     subscription_id = state["subscription_id"]
     subscription = LanSwitchInterconnect.from_subscription(subscription_id)
     assert subscription.status == "terminated"
+    assert mock_find_host.call_count == 2
+    assert mock_delete_network.call_count == 1
diff --git a/test/workflows/lan_switch_interconnect/test_validate_lan_switch_interconnect.py b/test/workflows/lan_switch_interconnect/test_validate_lan_switch_interconnect.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2e81ff21b1cc0bc9829bd5f28fb02223b6905beb 100644
--- a/test/workflows/lan_switch_interconnect/test_validate_lan_switch_interconnect.py
+++ b/test/workflows/lan_switch_interconnect/test_validate_lan_switch_interconnect.py
@@ -0,0 +1,48 @@
+from unittest.mock import patch
+
+import pytest
+from infoblox_client import objects
+
+from gso.products.product_types.lan_switch_interconnect import LanSwitchInterconnect
+from test.workflows import assert_complete, assert_lso_success, extract_state, run_workflow
+
+
+@pytest.mark.workflow()
+@patch("gso.services.infoblox.find_host_by_fqdn")
+@patch("gso.services.infoblox.find_network_by_cidr")
+@patch("gso.services.lso_client._send_request")
+def test_validate_lan_switch_interconnect(
+    mock_lso_interaction, mock_find_network, mock_find_host, lan_switch_interconnect_subscription_factory, faker
+):
+    subscription_id = lan_switch_interconnect_subscription_factory()
+    mocked_netbox_reply = objects.HostRecord(
+        connector=None,
+        aliases=[],
+        comment=subscription_id,
+        ipv4addrs=[
+            objects.IPv4(
+                ipv4addr=str(faker.ipv4()),
+                configure_for_dhcp=False,
+                mac="00:00:00:00:00:00",
+                ip=str(faker.ipv4()),
+                host=faker.domain_name(levels=4),
+            ),
+        ],
+        name=faker.domain_name(levels=4),
+    )
+    mock_find_host.return_value = mocked_netbox_reply
+    mock_find_network.return_value = mocked_netbox_reply
+    initial_lan_switch_interconnect_data = [{"subscription_id": subscription_id}]
+    result, process_stat, step_log = run_workflow(
+        "validate_lan_switch_interconnect", initial_lan_switch_interconnect_data
+    )
+    result, _ = assert_lso_success(result, process_stat, step_log)
+    assert_complete(result)
+
+    state = extract_state(result)
+    subscription_id = state["subscription_id"]
+    subscription = LanSwitchInterconnect.from_subscription(subscription_id)
+    assert subscription.status == "active"
+    assert subscription.insync is True
+    assert mock_find_host.call_count == 2
+    assert mock_find_network.call_count == 1
diff --git a/test/workflows/site/test_create_imported_site.py b/test/workflows/site/test_create_imported_site.py
index 4e8b8e35cd6a167e02e1d352440688eb2fc517b5..e63f44c62a02d67c6e313e696daa9a239439a0bc 100644
--- a/test/workflows/site/test_create_imported_site.py
+++ b/test/workflows/site/test_create_imported_site.py
@@ -19,6 +19,7 @@ def workflow_input_data(faker):
         "site_internal_id": faker.pyint(),
         "site_tier": SiteTier.TIER1,
         "site_ts_address": faker.ipv4(),
+        "site_contains_optical_equipment": True,
         "partner": "GEANT",
     }
 
diff --git a/test/workflows/switch/test_create_imported_switch.py b/test/workflows/switch/test_create_imported_switch.py
index 76ad61c7a80615d4b88ae94add85c5ddabf3bcd8..b38a0c9401066e88e15b0a11c7699d96dedf75bf 100644
--- a/test/workflows/switch/test_create_imported_switch.py
+++ b/test/workflows/switch/test_create_imported_switch.py
@@ -19,7 +19,7 @@ def workflow_input_data(faker, site_subscription_factory):
         "ts_port": faker.port_number(is_user=True),
         "site": site_subscription_factory(),
         "switch_vendor": Vendor.JUNIPER,
-        "switch_model": SwitchModel.EX3400,
+        "switch_model": SwitchModel.EX3400_24T,
     }
 
 
diff --git a/test/workflows/switch/test_create_switch.py b/test/workflows/switch/test_create_switch.py
index c11edf80bc4f3059957c2e697be3d536ca7fdbd6..64006141f05f22a12cb74ea76536266665720345 100644
--- a/test/workflows/switch/test_create_switch.py
+++ b/test/workflows/switch/test_create_switch.py
@@ -3,6 +3,7 @@ from unittest.mock import patch
 import pytest
 
 from gso.products import ProductName
+from gso.products.product_blocks.switch import SwitchModel
 from gso.products.product_types.switch import Switch
 from gso.services.subscriptions import get_product_id_by_name
 from test import USER_CONFIRM_EMPTY_FORM
@@ -32,6 +33,7 @@ def test_create_switch_success(
             "switch_site": site_subscription_factory(),
             "hostname": faker.domain_word(),
             "ts_port": faker.port_number(is_user=True),
+            "model": SwitchModel.EX3400_24T,
         },
         {},
     ]
diff --git a/test/workflows/vrf/test_modify_vrf_router_list.py b/test/workflows/vrf/test_modify_vrf_router_list.py
index 97f046561e503cb31eecd0e3f441911fc1a8318d..7a5bd14f79a52323580dbf8a5549654e4c98093d 100644
--- a/test/workflows/vrf/test_modify_vrf_router_list.py
+++ b/test/workflows/vrf/test_modify_vrf_router_list.py
@@ -1,22 +1,28 @@
 import uuid
+from unittest.mock import patch
 
 import pytest
 from pydantic_forms.exceptions import FormValidationError
 
 from gso.products.product_types.vrf import VRF
-from test.workflows import assert_complete, extract_state, run_workflow
+from test.workflows import assert_complete, assert_lso_interaction_success, extract_state, run_workflow
 
 
 @pytest.mark.workflow()
-def test_modify_vrf_router_list(vrf_subscription_factory, router_subscription_factory, faker):
+@patch("gso.services.lso_client._send_request")
+def test_modify_vrf_router_list(mock_lso_call, vrf_subscription_factory, router_subscription_factory, faker):
     subscription_id = vrf_subscription_factory()
     initial_vrf_data = [
         {"subscription_id": subscription_id},
         {
             "router_list": [{"router_id": router_subscription_factory()}, {"router_id": router_subscription_factory()}],
+            "tt_number": faker.tt_number(),
         },
     ]
-    result, _, _ = run_workflow("modify_vrf_router_list", initial_vrf_data)
+    result, process_stat, step_log = run_workflow("modify_vrf_router_list", initial_vrf_data)
+    for _ in range(2):
+        result, step_log = assert_lso_interaction_success(result, process_stat, step_log)
+
     assert_complete(result)
 
     state = extract_state(result)
@@ -24,6 +30,7 @@ def test_modify_vrf_router_list(vrf_subscription_factory, router_subscription_fa
     subscription = VRF.from_subscription(subscription_id)
     assert subscription.status == "active"
     assert len(subscription.vrf.vrf_router_list) == 2
+    assert mock_lso_call.call_count == 2
 
 
 @pytest.mark.workflow()
@@ -31,9 +38,7 @@ def test_modify_vrf_router_list_with_invalid_router_id(vrf_subscription_factory,
     subscription_id = vrf_subscription_factory()
     initial_vrf_data = [
         {"subscription_id": subscription_id},
-        {
-            "router_list": [{"router_id": uuid.uuid4()}],
-        },
+        {"router_list": [{"router_id": uuid.uuid4()}], "tt_number": faker.tt_number()},
     ]
 
     with pytest.raises(FormValidationError, match="Input should be an instance of Select a router"):
@@ -46,9 +51,7 @@ def test_modify_vrf_router_list_with_duplicate_router_id(vrf_subscription_factor
     router_id = router_subscription_factory()
     initial_vrf_data = [
         {"subscription_id": subscription_id},
-        {
-            "router_list": [{"router_id": router_id}, {"router_id": router_id}],
-        },
+        {"router_list": [{"router_id": router_id}, {"router_id": router_id}], "tt_number": faker.tt_number()},
     ]
 
     with pytest.raises(FormValidationError, match="List must be unique"):