diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py
index 551d35c7ba8f5386831871fe70fdc962633e1a18..eaf8f715aeff435b67bce77928e726daeb4729e2 100644
--- a/src/service/service/service_handlers/__init__.py
+++ b/src/service/service/service_handlers/__init__.py
@@ -20,6 +20,7 @@ from .l2nm_openconfig.L2NMOpenConfigServiceHandler import L2NMOpenConfigServiceH
 from .l3nm_emulated.L3NMEmulatedServiceHandler import L3NMEmulatedServiceHandler
 from .l3nm_openconfig.L3NMOpenConfigServiceHandler import L3NMOpenConfigServiceHandler
 from .l3nm_gnmi_openconfig.L3NMGnmiOpenConfigServiceHandler import L3NMGnmiOpenConfigServiceHandler
+from .l3nm_ietf_actn.L3NMIetfActnServiceHandler import L3NMIetfActnServiceHandler
 from .microwave.MicrowaveServiceHandler import MicrowaveServiceHandler
 from .p4.p4_service_handler import P4ServiceHandler
 from .tapi_tapi.TapiServiceHandler import TapiServiceHandler
@@ -57,6 +58,12 @@ SERVICE_HANDLERS = [
             FilterFieldEnum.DEVICE_DRIVER : DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG,
         }
     ]),
+    (L3NMIetfActnServiceHandler, [
+        {
+            FilterFieldEnum.SERVICE_TYPE  : ServiceTypeEnum.SERVICETYPE_L3NM,
+            FilterFieldEnum.DEVICE_DRIVER : DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN,
+        }
+    ]),
     (TapiServiceHandler, [
         {
             FilterFieldEnum.SERVICE_TYPE  : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE,
diff --git a/src/service/service/service_handlers/l3nm_ietf_actn/ConfigRuleComposer.py b/src/service/service/service_handlers/l3nm_ietf_actn/ConfigRuleComposer.py
new file mode 100644
index 0000000000000000000000000000000000000000..deb096b06f1e1af32c11051dabc285489ee2ca87
--- /dev/null
+++ b/src/service/service/service_handlers/l3nm_ietf_actn/ConfigRuleComposer.py
@@ -0,0 +1,128 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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.
+
+from typing import Dict, List, Optional, Tuple
+from common.proto.context_pb2 import Device, EndPoint
+from common.tools.object_factory.ConfigRule import json_config_rule_delete, json_config_rule_set
+
+from service.service.service_handler_api.AnyTreeTools import TreeNode
+
+def _interface(
+    if_name : str, ipv4_address : str, ipv4_prefix_length : int, enabled : bool,
+    vlan_id : Optional[int] = None, sif_index : Optional[int] = 1
+) -> Tuple[str, Dict]:
+    str_path = '/interface[{:s}]'.format(if_name)
+    data = {
+        'name': if_name, 'enabled': enabled, 'sub_if_index': sif_index,
+        'sub_if_enabled': enabled, 'sub_if_ipv4_enabled': enabled,
+        'sub_if_ipv4_address': ipv4_address, 'sub_if_ipv4_prefix_length': ipv4_prefix_length
+    }
+    if vlan_id is not None: data['sub_if_vlan'] = vlan_id
+    return str_path, data
+
+def _network_instance(ni_name, ni_type) -> Tuple[str, Dict]:
+    str_path = '/network_instance[{:s}]'.format(ni_name)
+    data = {'name': ni_name, 'type': ni_type}
+    return str_path, data
+
+def _network_instance_static_route(ni_name, prefix, next_hop, next_hop_index=0) -> Tuple[str, Dict]:
+    str_path = '/network_instance[{:s}]/static_route[{:s}]'.format(ni_name, prefix)
+    data = {'name': ni_name, 'prefix': prefix, 'next_hop': next_hop, 'next_hop_index': next_hop_index}
+    return str_path, data
+
+def _network_instance_interface(ni_name, if_name, sif_index) -> Tuple[str, Dict]:
+    str_path = '/network_instance[{:s}]/interface[{:s}.{:d}]'.format(ni_name, if_name, sif_index)
+    data = {'name': ni_name, 'if_name': if_name, 'sif_index': sif_index}
+    return str_path, data
+
+class EndpointComposer:
+    def __init__(self, endpoint_uuid : str) -> None:
+        self.uuid = endpoint_uuid
+        self.objekt : Optional[EndPoint] = None
+        self.sub_interface_index = 0
+        self.ipv4_address = None
+        self.ipv4_prefix_length = None
+        self.sub_interface_vlan_id = 0
+
+    def configure(self, endpoint_obj : EndPoint, settings : Optional[TreeNode]) -> None:
+        self.objekt = endpoint_obj
+        if settings is None: return
+        json_settings : Dict = settings.value
+        self.ipv4_address = json_settings['ipv4_address']
+        self.ipv4_prefix_length = json_settings['ipv4_prefix_length']
+        self.sub_interface_index = json_settings['sub_interface_index']
+        self.sub_interface_vlan_id = json_settings['sub_interface_vlan_id']
+
+    def get_config_rules(self, network_instance_name : str, delete : bool = False) -> List[Dict]:
+        json_config_rule = json_config_rule_delete if delete else json_config_rule_set
+        return [
+            json_config_rule(*_interface(
+                self.objekt.name, self.ipv4_address, self.ipv4_prefix_length, True,
+                sif_index=self.sub_interface_index, vlan_id=self.sub_interface_vlan_id,
+            )),
+            json_config_rule(*_network_instance_interface(
+                network_instance_name, self.objekt.name, self.sub_interface_index
+            )),
+        ]
+
+class DeviceComposer:
+    def __init__(self, device_uuid : str) -> None:
+        self.uuid = device_uuid
+        self.objekt : Optional[Device] = None
+        self.endpoints : Dict[str, EndpointComposer] = dict()
+        self.static_routes : Dict[str, str] = dict()
+    
+    def get_endpoint(self, endpoint_uuid : str) -> EndpointComposer:
+        if endpoint_uuid not in self.endpoints:
+            self.endpoints[endpoint_uuid] = EndpointComposer(endpoint_uuid)
+        return self.endpoints[endpoint_uuid]
+
+    def configure(self, device_obj : Device, settings : Optional[TreeNode]) -> None:
+        self.objekt = device_obj
+        if settings is None: return
+        json_settings : Dict = settings.value
+        static_routes = json_settings.get('static_routes', [])
+        for static_route in static_routes:
+            prefix   = static_route['prefix']
+            next_hop = static_route['next_hop']
+            self.static_routes[prefix] = next_hop
+
+    def get_config_rules(self, network_instance_name : str, delete : bool = False) -> List[Dict]:
+        json_config_rule = json_config_rule_delete if delete else json_config_rule_set
+        config_rules = [
+            json_config_rule(*_network_instance(network_instance_name, 'L3VRF'))
+        ]
+        for endpoint in self.endpoints.values():
+            config_rules.extend(endpoint.get_config_rules(network_instance_name, delete=delete))
+        for prefix, next_hop in self.static_routes.items():
+            config_rules.append(
+                json_config_rule(*_network_instance_static_route(network_instance_name, prefix, next_hop))
+            )
+        if delete: config_rules = list(reversed(config_rules))
+        return config_rules
+
+class ConfigRuleComposer:
+    def __init__(self) -> None:
+        self.devices : Dict[str, DeviceComposer] = dict()
+
+    def get_device(self, device_uuid : str) -> DeviceComposer:
+        if device_uuid not in self.devices:
+            self.devices[device_uuid] = DeviceComposer(device_uuid)
+        return self.devices[device_uuid]
+
+    def get_config_rules(self, network_instance_name : str, delete : bool = False) -> Dict[str, List[Dict]]:
+        return {
+            device_uuid : device.get_config_rules(network_instance_name, delete=delete)
+            for device_uuid, device in self.devices.items()
+        }
diff --git a/src/service/service/service_handlers/l3nm_ietf_actn/L3NMIetfActnServiceHandler.py b/src/service/service/service_handlers/l3nm_ietf_actn/L3NMIetfActnServiceHandler.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b53ac0d27ed8413d9ddd947b055c5623b93d429
--- /dev/null
+++ b/src/service/service/service_handlers/l3nm_ietf_actn/L3NMIetfActnServiceHandler.py
@@ -0,0 +1,161 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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.
+
+import json, logging
+from typing import Any, Dict, List, Optional, Tuple, Union
+from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method
+from common.proto.context_pb2 import ConfigRule, DeviceId, Service
+from common.tools.object_factory.Device import json_device_id
+from common.type_checkers.Checkers import chk_type
+from service.service.service_handler_api.Tools import get_device_endpoint_uuids, get_endpoint_matching
+from service.service.service_handler_api._ServiceHandler import _ServiceHandler
+from service.service.service_handler_api.SettingsHandler import SettingsHandler
+from service.service.task_scheduler.TaskExecutor import TaskExecutor
+from .ConfigRuleComposer import ConfigRuleComposer
+
+LOGGER = logging.getLogger(__name__)
+
+METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'l3nm_ietf_actn'})
+
+class L3NMIetfActnServiceHandler(_ServiceHandler):
+    def __init__(   # pylint: disable=super-init-not-called
+        self, service : Service, task_executor : TaskExecutor, **settings
+    ) -> None:
+        self.__service = service
+        self.__task_executor = task_executor
+        self.__settings_handler = SettingsHandler(service.service_config, **settings)
+        self.__composer = ConfigRuleComposer()
+        self.__endpoint_map : Dict[Tuple[str, str], str] = dict()
+
+    def _compose_config_rules(self, endpoints : List[Tuple[str, str, Optional[str]]]) -> None:
+        for endpoint in endpoints:
+            device_uuid, endpoint_uuid = get_device_endpoint_uuids(endpoint)
+
+            device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid)))
+            device_settings = self.__settings_handler.get_device_settings(device_obj)
+            _device = self.__composer.get_device(device_obj.name)
+            _device.configure(device_obj, device_settings)
+
+            endpoint_obj = get_endpoint_matching(device_obj, endpoint_uuid)
+            endpoint_settings = self.__settings_handler.get_endpoint_settings(device_obj, endpoint_obj)
+            _endpoint = _device.get_endpoint(endpoint_obj.name)
+            _endpoint.configure(endpoint_obj, endpoint_settings)
+
+            self.__endpoint_map[(device_uuid, endpoint_uuid)] = device_obj.name
+
+    def _do_configurations(
+        self, config_rules_per_device : Dict[str, List[Dict]], endpoints : List[Tuple[str, str, Optional[str]]],
+        delete : bool = False
+    ) -> List[Union[bool, Exception]]:
+        # Configuration is done atomically on each device, all OK / all KO per device
+        results_per_device = dict()
+        for device_name,json_config_rules in config_rules_per_device.items():
+            try:
+                device_obj = self.__composer.get_device(device_name).objekt
+                if len(json_config_rules) == 0: continue
+                del device_obj.device_config.config_rules[:]
+                for json_config_rule in json_config_rules:
+                    device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
+                self.__task_executor.configure_device(device_obj)
+                results_per_device[device_name] = True
+            except Exception as e: # pylint: disable=broad-exception-caught
+                verb = 'deconfigure' if delete else 'configure'
+                MSG = 'Unable to {:s} Device({:s}) : ConfigRules({:s})'
+                LOGGER.exception(MSG.format(verb, str(device_name), str(json_config_rules)))
+                results_per_device[device_name] = e
+
+        results = []
+        for endpoint in endpoints:
+            device_uuid, endpoint_uuid = get_device_endpoint_uuids(endpoint)
+            device_name = self.__endpoint_map[(device_uuid, endpoint_uuid)]
+            results.append(results_per_device[device_name])
+        return results
+
+    @metered_subclass_method(METRICS_POOL)
+    def SetEndpoint(
+        self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None
+    ) -> List[Union[bool, Exception]]:
+        chk_type('endpoints', endpoints, list)
+        if len(endpoints) == 0: return []
+        service_uuid = self.__service.service_id.service_uuid.uuid
+        #settings = self.__settings_handler.get('/settings')
+        self._compose_config_rules(endpoints)
+        network_instance_name = service_uuid.split('-')[0]
+        config_rules_per_device = self.__composer.get_config_rules(network_instance_name, delete=False)
+        results = self._do_configurations(config_rules_per_device, endpoints)
+        return results
+
+    @metered_subclass_method(METRICS_POOL)
+    def DeleteEndpoint(
+        self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None
+    ) -> List[Union[bool, Exception]]:
+        chk_type('endpoints', endpoints, list)
+        if len(endpoints) == 0: return []
+        service_uuid = self.__service.service_id.service_uuid.uuid
+        #settings = self.__settings_handler.get('/settings')
+        self._compose_config_rules(endpoints)
+        network_instance_name = service_uuid.split('-')[0]
+        config_rules_per_device = self.__composer.get_config_rules(network_instance_name, delete=True)
+        results = self._do_configurations(config_rules_per_device, endpoints, delete=True)
+        return results
+
+    @metered_subclass_method(METRICS_POOL)
+    def SetConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+        chk_type('constraints', constraints, list)
+        if len(constraints) == 0: return []
+
+        msg = '[SetConstraint] Method not implemented. Constraints({:s}) are being ignored.'
+        LOGGER.warning(msg.format(str(constraints)))
+        return [True for _ in range(len(constraints))]
+
+    @metered_subclass_method(METRICS_POOL)
+    def DeleteConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+        chk_type('constraints', constraints, list)
+        if len(constraints) == 0: return []
+
+        msg = '[DeleteConstraint] Method not implemented. Constraints({:s}) are being ignored.'
+        LOGGER.warning(msg.format(str(constraints)))
+        return [True for _ in range(len(constraints))]
+
+    @metered_subclass_method(METRICS_POOL)
+    def SetConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+        chk_type('resources', resources, list)
+        if len(resources) == 0: return []
+
+        results = []
+        for resource in resources:
+            try:
+                resource_value = json.loads(resource[1])
+                self.__settings_handler.set(resource[0], resource_value)
+                results.append(True)
+            except Exception as e: # pylint: disable=broad-except
+                LOGGER.exception('Unable to SetConfig({:s})'.format(str(resource)))
+                results.append(e)
+
+        return results
+
+    @metered_subclass_method(METRICS_POOL)
+    def DeleteConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+        chk_type('resources', resources, list)
+        if len(resources) == 0: return []
+
+        results = []
+        for resource in resources:
+            try:
+                self.__settings_handler.delete(resource[0])
+            except Exception as e: # pylint: disable=broad-except
+                LOGGER.exception('Unable to DeleteConfig({:s})'.format(str(resource)))
+                results.append(e)
+
+        return results
diff --git a/src/service/service/service_handlers/l3nm_ietf_actn/__init__.py b/src/service/service/service_handlers/l3nm_ietf_actn/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612
--- /dev/null
+++ b/src/service/service/service_handlers/l3nm_ietf_actn/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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.
+