diff --git a/src/device/service/drivers/ietf_actn/IetfActnDriver.py b/src/device/service/drivers/ietf_actn/IetfActnDriver.py
index c31bd85b92c3421b8cdc6001a25f9a2e5e2a3b47..6d0aada4e2ae512192ac79799cdebe1cb4b14fac 100644
--- a/src/device/service/drivers/ietf_actn/IetfActnDriver.py
+++ b/src/device/service/drivers/ietf_actn/IetfActnDriver.py
@@ -13,7 +13,6 @@
 # limitations under the License.
 
 import json, logging, requests, threading
-from requests.auth import HTTPBasicAuth
 from typing import Any, Iterator, List, Optional, Tuple, Union
 from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method
 from common.type_checkers.Checkers import chk_string, chk_type
@@ -27,8 +26,6 @@ LOGGER = logging.getLogger(__name__)
 DRIVER_NAME = 'ietf_actn'
 METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': DRIVER_NAME})
 
-DEFAULT_BASE_URL = '/restconf/data'
-DEFAULT_TIMEOUT = 120
 
 class IetfActnDriver(_Driver):
     def __init__(self, address: str, port: int, **settings) -> None:
diff --git a/src/device/service/drivers/ietf_actn/handlers/EthService.py b/src/device/service/drivers/ietf_actn/handlers/EthService.py
index 0d923b16cd7bcc9c0cb1c01e38c66336a1c78cd1..e8fe9817bcf2d0f3eedf87c5f79d5e1de2030861 100644
--- a/src/device/service/drivers/ietf_actn/handlers/EthService.py
+++ b/src/device/service/drivers/ietf_actn/handlers/EthService.py
@@ -12,10 +12,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import enum
-from typing import Dict, List, Tuple
+import enum, json, logging, operator, requests
+from typing import Any, Dict, List, Tuple, Union
+from device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_SERVICES
+from .RestApiClient import RestApiClient
 
-OSU_TUNNEL_URL = '/restconf/data/ietf-te:tunnel'
+LOGGER = logging.getLogger(__name__)
 
 class BandwidthProfileTypeEnum(enum.Enum):
     MEF_10_BWP = 'ietf-eth-tran-types:mef-10-bwp'
@@ -104,3 +106,41 @@ def compose_etht_service(
         'svc-tunnel': [{'tunnel-name': osu_tunnel_name}],
         'optimizations': compose_optimizations(),
     }]}}
+
+class EthtServiceHandler:
+    def __init__(self, rest_api_client : RestApiClient) -> None:
+        self._rest_api_client = rest_api_client
+        self._object_name     = 'EthtService'
+        self._subpath_url     = '/ietf-eth-tran-service:etht-svc/etht-svc-instances'
+
+    def get(self) -> List[Tuple[str, Any]]:
+        pass
+
+    def update(self, parameters : Dict) -> List[Union[bool, Exception]]:
+        name              = parameters['name'           ]
+        service_type      = parameters['service_type'   ]
+        osu_tunnel_name   = parameters['osu_tunnel_name']
+
+        src_node_id       = parameters['src_node_id'    ]
+        src_tp_id         = parameters['src_tp_id'      ]
+        src_vlan_tag      = parameters['src_vlan_tag'   ]
+        src_static_routes = parameters.get('src_static_routes', [])
+
+        dst_node_id       = parameters['dst_node_id'    ]
+        dst_tp_id         = parameters['dst_tp_id'      ]
+        dst_vlan_tag      = parameters['dst_vlan_tag'   ]
+        dst_static_routes = parameters.get('dst_static_routes', [])
+
+        service_type = ServiceTypeEnum._value2member_map_[service_type]
+
+        data = compose_etht_service(
+            name, service_type, osu_tunnel_name,
+            src_node_id, src_tp_id, src_vlan_tag, dst_node_id, dst_tp_id, dst_vlan_tag,
+            src_static_routes=src_static_routes, dst_static_routes=dst_static_routes
+        )
+
+        return self._rest_api_client.update(self._object_name, self._subpath_url, data)
+
+    def delete(self, etht_service_name : str) -> List[Union[bool, Exception]]:
+        filters = [('etht-svc-name', etht_service_name)]
+        return self._rest_api_client.delete(self._object_name, self._subpath_url, filters)
diff --git a/src/device/service/drivers/ietf_actn/handlers/OsuTunnel.py b/src/device/service/drivers/ietf_actn/handlers/OsuTunnel.py
index a15f73eab7e550d3ba43447ba260cee19daf7fff..d6332a8d7cc6aa660b0959f328e3a8d5ad69ebfa 100644
--- a/src/device/service/drivers/ietf_actn/handlers/OsuTunnel.py
+++ b/src/device/service/drivers/ietf_actn/handlers/OsuTunnel.py
@@ -13,15 +13,12 @@
 # limitations under the License.
 
 import enum, json, logging, operator, requests
-from requests.auth import HTTPBasicAuth
-from typing import Dict, List, Optional
+from typing import Any, Dict, List, Tuple, Union
 from device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_SERVICES
-from .Tools import HTTP_OK_CODES
+from .RestApiClient import RestApiClient
 
 LOGGER = logging.getLogger(__name__)
 
-BASE_URL_OSU_TUNNEL = '{:s}/ietf-te:tunnel'
-
 class EndpointProtectionRoleEnum(enum.Enum):
     WORK = 'work'
 
@@ -84,11 +81,14 @@ def compose_osu_tunnel(
         'protection': compose_osu_tunnel_protection(),
     }]}
 
-class OsuTunnel:
-    def __init__(self, base_url : str, auth : Optional[HTTPBasicAuth] = None, timeout : Optional[int] = None) -> None:
-        self._base_url = base_url
-        self._auth     = auth
-        self._timeout  = timeout
+class OsuTunnelHandler:
+    def __init__(self, rest_api_client : RestApiClient) -> None:
+        self._rest_api_client = rest_api_client
+        self._object_name     = 'OsuTunnel'
+        self._subpath_url     = '/ietf-te:tunnel'
+
+    def get(self) -> List[Tuple[str, Any]]:
+        pass
 
     def get(self, resource_key : str) -> None:
         url = '{:s}/restconf/data/tapi-common:context'.format(base_url)
@@ -110,38 +110,7 @@ class OsuTunnel:
             result.append((resource_key, e))
             return result
 
-        if resource_key == RESOURCE_ENDPOINTS:
-            if 'tapi-common:context' in context:
-                context = context['tapi-common:context']
-            elif 'context' in context:
-                context = context['context']
-
-            for sip in context['service-interface-point']:
-                layer_protocol_name = sip.get('layer-protocol-name', '?')
-                supportable_spectrum = sip.get('tapi-photonic-media:media-channel-service-interface-point-spec', {})
-                supportable_spectrum = supportable_spectrum.get('mc-pool', {})
-                supportable_spectrum = supportable_spectrum.get('supportable-spectrum', [])
-                supportable_spectrum = supportable_spectrum[0] if len(supportable_spectrum) == 1 else {}
-                grid_type = supportable_spectrum.get('frequency-constraint', {}).get('grid-type')
-                granularity = supportable_spectrum.get('frequency-constraint', {}).get('adjustment-granularity')
-                direction = sip.get('direction', '?')
-
-                endpoint_type = [layer_protocol_name, grid_type, granularity, direction]
-                str_endpoint_type = ':'.join(filter(lambda i: operator.is_not(i, None), endpoint_type))
-                sip_uuid = sip['uuid']
-
-                sip_names = sip.get('name', [])
-                sip_name = next(iter([
-                    sip_name['value']
-                    for sip_name in sip_names
-                    if sip_name['value-name'] == 'local-name'
-                ]), sip_uuid)
-
-                endpoint_url = '/endpoints/endpoint[{:s}]'.format(sip_uuid)
-                endpoint_data = {'uuid': sip_uuid, 'name': sip_name, 'type': str_endpoint_type}
-                result.append((endpoint_url, endpoint_data))
-
-        elif resource_key == RESOURCE_SERVICES:
+        if resource_key == RESOURCE_SERVICES:
             if 'tapi-common:context' in context:
                 context = context['tapi-common:context']
             elif 'context' in context:
@@ -181,56 +150,31 @@ class OsuTunnel:
 
         return result
 
-    def update(self, resource_value : Dict) -> None:
-        name                 = resource_value['name'                ]
-        src_node_id          = resource_value['src_node_id'         ]
-        src_tp_id            = resource_value['src_tp_id'           ]
-        src_ttp_channel_name = resource_value['src_ttp_channel_name']
-        dst_node_id          = resource_value['dst_node_id'         ]
-        dst_tp_id            = resource_value['dst_tp_id'           ]
-        dst_ttp_channel_name = resource_value['dst_ttp_channel_name']
-        odu_type             = resource_value.get('odu_type',       OduTypeEnum.OSUFLEX.value)
-        osuflex_number       = resource_value.get('osuflex_number', 1                        )
-        delay                = resource_value.get('delay',          20                       )
-        bidirectional        = resource_value.get('bidirectional',  True                     )
+    def update(self, parameters : Dict) -> List[Union[bool, Exception]]:
+        name                 = parameters['name'                ]
+
+        src_node_id          = parameters['src_node_id'         ]
+        src_tp_id            = parameters['src_tp_id'           ]
+        src_ttp_channel_name = parameters['src_ttp_channel_name']
+
+        dst_node_id          = parameters['dst_node_id'         ]
+        dst_tp_id            = parameters['dst_tp_id'           ]
+        dst_ttp_channel_name = parameters['dst_ttp_channel_name']
+
+        odu_type             = parameters.get('odu_type',       OduTypeEnum.OSUFLEX.value)
+        osuflex_number       = parameters.get('osuflex_number', 1                        )
+        delay                = parameters.get('delay',          20                       )
+        bidirectional        = parameters.get('bidirectional',  True                     )
 
         odu_type = OduTypeEnum._value2member_map_[odu_type]
 
-        headers = {'content-type': 'application/json'}
         data = compose_osu_tunnel(
             name, src_node_id, src_tp_id, src_ttp_channel_name, dst_node_id, dst_tp_id, dst_ttp_channel_name,
             odu_type, osuflex_number, delay, bidirectional=bidirectional
         )
 
-        results = []
-        try:
-            LOGGER.info('OSU Tunnel {:s}: {:s}'.format(str(name), str(data)))
-            response = requests.post(
-                self._base_url, data=json.dumps(data), timeout=self._timeout,
-                headers=headers, verify=False, auth=self._auth
-            )
-            LOGGER.info('Response: {:s}'.format(str(response)))
-        except Exception as e:  # pylint: disable=broad-except
-            LOGGER.exception('Exception creating OsuTunnel(name={:s}, data={:s})'.format(str(name), str(data)))
-            results.append(e)
-        else:
-            if response.status_code not in HTTP_OK_CODES:
-                msg = 'Could not create OsuTunnel(name={:s}, data={:s}). status_code={:s} reply={:s}'
-                LOGGER.error(msg.format(str(name), str(data), str(response.status_code), str(response)))
-            results.append(response.status_code in HTTP_OK_CODES)
-        return results
-
-    def delete(self, osu_tunnel_name : str) -> List[]:
-        url = '{:s}[name={:s}]'.format(self._base_url, osu_tunnel_name)
-        results = []
-        try:
-            response = requests.delete(url=url, timeout=self._timeout, verify=False, auth=self._auth)
-        except Exception as e:  # pylint: disable=broad-except
-            LOGGER.exception('Exception deleting OsuTunnel(name={:s})'.format(str(osu_tunnel_name)))
-            results.append(e)
-        else:
-            if response.status_code not in HTTP_OK_CODES:
-                msg = 'Could not delete OsuTunnel(name={:s}). status_code={:s} reply={:s}'
-                LOGGER.error(msg.format(str(osu_tunnel_name), str(response.status_code), str(response)))
-            results.append(response.status_code in HTTP_OK_CODES)
-        return results
+        return self._rest_api_client.update(self._object_name, self._subpath_url, data)
+
+    def delete(self, osu_tunnel_name : str) -> List[Union[bool, Exception]]:
+        filters = [('name', osu_tunnel_name)]
+        return self._rest_api_client.delete(self._object_name, self._subpath_url, filters)
diff --git a/src/device/service/drivers/ietf_actn/handlers/RestApiClient.py b/src/device/service/drivers/ietf_actn/handlers/RestApiClient.py
new file mode 100644
index 0000000000000000000000000000000000000000..8660f35ce1a6a8bd42cddcc2d0a50f81568fe27c
--- /dev/null
+++ b/src/device/service/drivers/ietf_actn/handlers/RestApiClient.py
@@ -0,0 +1,143 @@
+# 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 copy, json, logging, requests
+from requests.auth import HTTPBasicAuth
+from typing import Any, Dict, List, Tuple, Union
+
+LOGGER = logging.getLogger(__name__)
+
+DEFAULT_BASE_URL = '/restconf/data'
+DEFAULT_SCHEMA   = 'http'
+DEFAULT_TIMEOUT  = 120
+DEFAULT_VERIFY   = False
+
+HTTP_OK_CODES = {
+    200,    # OK
+    201,    # Created
+    202,    # Accepted
+    204,    # No Content
+}
+
+class RestApiClient:
+    def __init__(self, address : str, port : int, settings : Dict[str, Any] = dict()) -> None:
+        username = settings.get('username')
+        password = settings.get('password')
+        self._auth = HTTPBasicAuth(username, password) if username is not None and password is not None else None
+
+        scheme   = settings.get('scheme',   DEFAULT_SCHEMA  )
+        base_url = settings.get('base_url', DEFAULT_BASE_URL)
+        self._base_url = '{:s}://{:s}:{:d}{:s}'.format(scheme, address, int(port), base_url)
+
+        self._timeout = int(settings.get('timeout', DEFAULT_TIMEOUT))
+        self._verify  = int(settings.get('verify',  DEFAULT_VERIFY ))
+
+
+    def get(
+        self, object_name : str, url : str, filters : List[Tuple[str, str]]
+    ) -> List[Union[Any, Exception]]:
+        str_filters = ''.join([
+            '[{:s}={:s}]'.format(filter_field, filter_value)
+            for filter_field, filter_value in filters
+        ])
+
+        results = []
+        try:
+            MSG = 'Get {:s}({:s})'
+            LOGGER.info(MSG.format(str(object_name), str(str_filters)))
+            response = requests.get(
+                self._base_url + url + str_filters,
+                timeout=self._timeout, verify=self._verify, auth=self._auth
+            )
+            LOGGER.info('  Response: {:s}'.format(str(response)))
+        except Exception as e:  # pylint: disable=broad-except
+            MSG = 'Exception Getting {:s}({:s})'
+            LOGGER.exception(MSG.format(str(object_name), str(str_filters)))
+            results.append(e)
+            return results
+        else:
+            if response.status_code not in HTTP_OK_CODES:
+                MSG = 'Could not get {:s}({:s}): status_code={:s} reply={:s}'
+                msg = MSG.format(str(object_name), str(str_filters), str(response.status_code), str(response))
+                LOGGER.error(msg)
+                results.append(Exception(msg))
+                return results
+
+        try:
+            results.append(json.loads(response.content))
+        except Exception:  # pylint: disable=broad-except
+            MSG = 'Could not decode reply {:s}({:s}): {:s} {:s}'
+            msg = MSG.format(str(object_name), str(str_filters), str(response.status_code), str(response))
+            LOGGER.exception(msg)
+            results.append(Exception(msg))
+
+        return results
+
+    def update(
+        self, object_name : str, url : str, data : Dict, headers : Dict[str, Any] = dict()
+    ) -> List[Union[bool, Exception]]:
+        headers = copy.deepcopy(headers)
+        if 'content-type' not in {header_name.lower() for header_name in headers.keys()}:
+            headers.update({'content-type': 'application/json'})
+
+        results = []
+        try:
+            MSG = 'Create/Update {:s}({:s})'
+            LOGGER.info(MSG.format(str(object_name), str(data)))
+            response = requests.post(
+                self._base_url + url, data=json.dumps(data), headers=headers,
+                timeout=self._timeout, verify=self._verify, auth=self._auth
+            )
+            LOGGER.info('  Response: {:s}'.format(str(response)))
+        except Exception as e:  # pylint: disable=broad-except
+            MSG = 'Exception Creating/Updating {:s}({:s})'
+            LOGGER.exception(MSG.format(str(object_name), str(data)))
+            results.append(e)
+        else:
+            if response.status_code not in HTTP_OK_CODES:
+                MSG = 'Could not create/update {:s}({:s}): status_code={:s} reply={:s}'
+                LOGGER.error(MSG.format(str(object_name), str(data), str(response.status_code), str(response)))
+            results.append(response.status_code in HTTP_OK_CODES)
+
+        return results
+
+
+    def delete(
+        self, object_name : str, url : str, filters : List[Tuple[str, str]]
+    ) -> List[Union[bool, Exception]]:
+        str_filters = ''.join([
+            '[{:s}={:s}]'.format(filter_field, filter_value)
+            for filter_field, filter_value in filters
+        ])
+
+        results = []
+        try:
+            MSG = 'Delete {:s}({:s})'
+            LOGGER.info(MSG.format(str(object_name), str(str_filters)))
+            response = requests.delete(
+                self._base_url + url + str_filters,
+                timeout=self._timeout, verify=self._verify, auth=self._auth
+            )
+            LOGGER.info('  Response: {:s}'.format(str(response)))
+        except Exception as e:  # pylint: disable=broad-except
+            MSG = 'Exception Deleting {:s}({:s})'
+            LOGGER.exception(MSG.format(str(object_name), str(str_filters)))
+            results.append(e)
+        else:
+            if response.status_code not in HTTP_OK_CODES:
+                MSG = 'Could not delete {:s}({:s}): status_code={:s} reply={:s}'
+                LOGGER.error(MSG.format(str(object_name), str(str_filters), str(response.status_code), str(response)))
+            results.append(response.status_code in HTTP_OK_CODES)
+
+        return results
diff --git a/src/device/service/drivers/ietf_actn/handlers/Tools.py b/src/device/service/drivers/ietf_actn/handlers/Tools.py
deleted file mode 100644
index c14c65afab4001fa5c3935d1a7ef4893d43342bc..0000000000000000000000000000000000000000
--- a/src/device/service/drivers/ietf_actn/handlers/Tools.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# 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.
-
-HTTP_OK_CODES = {
-    200,    # OK
-    201,    # Created
-    202,    # Accepted
-    204,    # No Content
-}