diff --git a/inventory_provider/routes/poller.py b/inventory_provider/routes/poller.py
index 8497b2cc310e778f08c892b871cd0b928591c44b..ad87d6866e6f017371e2efcbca720cb52543af5b 100644
--- a/inventory_provider/routes/poller.py
+++ b/inventory_provider/routes/poller.py
@@ -82,3 +82,23 @@ def poller_interface_oids(hostname):
         result.append(ifc_data)
 
     return jsonify(result)
+
+
+@routes.route('/services/<category>', methods=['GET', 'POST'])
+@common.require_accepts_json
+def service_category_interfaces(category):
+
+    result = []
+
+    r = common.get_current_redis()
+    for k in r.scan_iter(f'interface-services:{category.lower()}:*'):
+        ifc = r.get(k.decode('utf-8'))
+        result.append(json.loads(ifc.decode('utf-8')))
+
+    if not result:
+        return Response(
+            response=f'no info available for service category {category}',
+            status=404,
+            mimetype="text/html")
+
+    return jsonify(result)
diff --git a/inventory_provider/tasks/worker.py b/inventory_provider/tasks/worker.py
index 33709d1f4d0ee3b219f32b99287b726f25ce8d37..e39f3d12f6ec6c6eaa6e22eab371925199db1972 100644
--- a/inventory_provider/tasks/worker.py
+++ b/inventory_provider/tasks/worker.py
@@ -560,6 +560,7 @@ def refresh_finalizer(self, pending_task_ids_json):
 
         _wait_for_tasks(task_ids, update_callback=_update)
         _build_subnet_db(update_callback=_update)
+        _build_interface_services(update_callback=_update)
 
     except (jsonschema.ValidationError,
             json.JSONDecodeError,
@@ -573,6 +574,54 @@ def refresh_finalizer(self, pending_task_ids_json):
     logger.debug('<<< refresh_finalizer')
 
 
+def _build_interface_services(update_callback=lambda s: None):
+    logger.debug('>>> _build_interface_services')
+
+    r = get_next_redis(InventoryTask.config)
+
+    def _interfaces():
+        for k in r.scan_iter('netconf-interfaces:*'):
+            k = k.decode('utf-8')
+            (_, router_name, ifc_name) = k.split(':')
+
+            info = r.get(k).decode('utf-8')
+            info = json.loads(info)
+
+            assert ifc_name == info['name']
+            yield {
+                'router': router_name,
+                'interface': info['name'],
+                'description': info['description']
+            }
+
+    def _classify(ifc):
+        if ifc['description'].startswith('SRV_MDVPN'):
+            return 'mdvpn'
+        if 'LHCONE' in ifc['description']:
+            return 'lhcone'
+        return None
+
+    r = get_next_redis(InventoryTask.config)
+    rp = r.pipeline()
+
+    update_callback('loading all known interfaces')
+    interfaces = list(_interfaces())
+    update_callback(f'loaded {len(interfaces)} interfaces, '
+                    'saving by service category')
+
+    for ifc in interfaces:
+        service_type = _classify(ifc)
+        if not service_type:
+            continue
+        rp.set(
+            f'interface-services:{service_type}'
+            f':{ifc["router"]}:{ifc["interface"]}',
+            json.dumps(ifc))
+
+    rp.execute()
+    logger.debug('<<< _build_interface_services')
+
+
 def _build_subnet_db(update_callback=lambda s: None):
 
     r = get_next_redis(InventoryTask.config)
diff --git a/test/test_general_poller_routes.py b/test/test_general_poller_routes.py
new file mode 100644
index 0000000000000000000000000000000000000000..0e88d1e660b919b4da39c4f07820b5c8dac8a3bd
--- /dev/null
+++ b/test/test_general_poller_routes.py
@@ -0,0 +1,54 @@
+import json
+
+import jsonschema
+import pytest
+
+from inventory_provider.tasks import worker
+
+DEFAULT_REQUEST_HEADERS = {
+    "Content-type": "application/json",
+    "Accept": ["application/json"]
+}
+
+
+INTERFACE_LIST_SCHEMA = {
+    '$schema': 'http://json-schema.org/draft-07/schema#',
+
+    'definitions': {
+        'ifc-info': {
+            'type': 'object',
+            'properties': {
+                'description': {'type': 'string'},
+                'router': {'type': 'string'},
+                'interface': {'type': 'string'}
+            },
+            'required': ['router', 'interface', 'description'],
+            'additionalProperties': False
+        },
+    },
+
+    'type': 'array',
+    'items': {'$ref': '#/definitions/ifc-info'}
+}
+
+
+@pytest.mark.parametrize('category', ['mdvpn', 'lhcone', 'MDVpn', 'LHCONE'])
+def test_service_category(client, mocked_worker_module, category):
+    worker._build_interface_services()
+    rv = client.get(
+        f'/poller/services/{category}',
+        headers=DEFAULT_REQUEST_HEADERS)
+    assert rv.status_code == 200
+    assert rv.is_json
+    response_data = json.loads(rv.data.decode('utf-8'))
+    jsonschema.validate(response_data, INTERFACE_LIST_SCHEMA)
+    assert response_data, 'expected a non-empty list'
+
+
+@pytest.mark.parametrize('category', ['mdvpn ', ' mdvpn', 'mdvpn1', 'aaa'])
+def test_service_category_not_found(client, mocked_worker_module, category):
+    worker._build_interface_services()
+    rv = client.get(
+        f'/poller/services/{category}',
+        headers=DEFAULT_REQUEST_HEADERS)
+    assert rv.status_code == 404
diff --git a/test/test_worker_utils.py b/test/test_worker_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..f94d2dab2d488d68a0b9375bf7ca74670ec97e86
--- /dev/null
+++ b/test/test_worker_utils.py
@@ -0,0 +1,106 @@
+"""
+tests of a few worker utilities
+"""
+import json
+import re
+
+import jsonschema
+
+from inventory_provider.tasks import worker
+from inventory_provider.tasks import common
+
+
+def backend_db():
+    return common._get_redis({
+        'redis': {
+            'hostname': None,
+            'port': None
+        },
+        'redis-databases': [0, 7]
+    }).db
+
+
+def test_build_interface_services(mocked_worker_module):
+    """
+    checks that valid interface service objects are created
+    :param mocked_worker_module: fixture
+    :return:
+    """
+    ifc_schema = {
+        '$schema': 'http://json-schema.org/draft-07/schema#',
+
+        'type': 'object',
+        'properties': {
+            'description': {'type': 'string'},
+            'router': {'type': 'string'},
+            'interface': {'type': 'string'}
+        },
+        'required': ['router', 'interface', 'description'],
+        'additionalProperties': False
+    }
+
+    db = backend_db()  # also forces initialization
+    worker._build_interface_services()
+
+    seen_types = set()
+    for k, v in db.items():
+        if not k.startswith('interface-services:'):
+            continue
+
+        (_, type, router, ifc_name) = k.split(':')
+
+        ifc_info = json.loads(v)
+        jsonschema.validate(json.loads(v), ifc_schema)
+
+        assert ifc_info['router'] == router
+        assert ifc_info['interface'] == ifc_name
+
+        seen_types.add(type)
+
+    assert type in ('mdvpn', 'lhcone')
+
+    expected_seen_types = set(['mdvpn', 'lhcone'])
+    assert seen_types == expected_seen_types
+
+
+def test_build_subnet_db(mocked_worker_module):
+    """
+    checks that valid reverse subnet objects are created
+    :param mocked_worker_module: fixture
+    :return:
+    """
+
+    address_schema = {
+        '$schema': 'http://json-schema.org/draft-07/schema#',
+
+        'type': 'object',
+        'properties': {
+            'name': {'type': 'string'},
+            'interface address': {'type': 'string'},
+            'interface name': {'type': 'string'},
+            'router': {'type': 'string'}
+        },
+        'required': ['name', 'interface address', 'interface name', 'router'],
+        'additionalProperties': False
+    }
+
+    db = backend_db()  # also forces initialization
+    worker._build_subnet_db()
+
+    found_record = False
+    for key, value in db.items():
+
+        if not key.startswith('reverse_interface_addresses:'):
+            continue
+
+        found_record = True
+
+        m = re.match('^reverse_interface_addresses:(.+)', key)
+        assert m
+        address = m.group(1)
+
+        value = json.loads(value)
+        jsonschema.validate(value, address_schema)
+        assert value['name'] == address
+
+    assert found_record