diff --git a/Changelog.md b/Changelog.md
index 944ddac918962f57821d2e20eaf6356a4e866cd4..178d74d11ea6ad368c68fc798669e7cb1c98ae8b 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -2,7 +2,15 @@
 
 All notable changes to this project will be documented in this file.
 
-## [0.62] - 2021-04-01
+## [0.65] - 2021-05-28
+- DBOARD3-438: Changed status of non-monitored services
+- DBOARD3-439: initial LnetD endpoint
+
+
+## [0.64] - 2021-05-19
+- DBOARD3-303: Replace dependency on OpsDB with IMS
+
+## [0.63] - 2021-04-01
 - POL1-370: load & return lab interfaces for poller
 
 ## [0.62] - 2021-03-24
diff --git a/docs/source/protocol/index.rst b/docs/source/protocol/index.rst
index e0ef1976524f092d255755ee35ba77f4ca5edbaf..0ec3dbb58ae1dd95b59fc4093a1ae47b4e794309 100644
--- a/docs/source/protocol/index.rst
+++ b/docs/source/protocol/index.rst
@@ -33,4 +33,5 @@ API modules
    lg
    data
    jobs
-   msr
\ No newline at end of file
+   msr
+   lnetd
\ No newline at end of file
diff --git a/docs/source/protocol/lnetd.rst b/docs/source/protocol/lnetd.rst
new file mode 100644
index 0000000000000000000000000000000000000000..6502451c057217084217ed77821b64e3945b8ea9
--- /dev/null
+++ b/docs/source/protocol/lnetd.rst
@@ -0,0 +1,14 @@
+.. LnetD endpoint docs
+
+
+LnetD support
+=========================
+
+This endpoint is intended for use with LnetD
+
+.. contents:: :local:
+
+/LnetD/interfaces</hostname>
+---------------------------------
+
+.. autofunction:: inventory_provider.routes.lnetd.interfaces
diff --git a/inventory_provider/__init__.py b/inventory_provider/__init__.py
index 5767a1da9c37b94dbc8446877ce9d4772e6c4d3b..aa92cb410dffe41600160d1d94bd1b98e3f9a75d 100644
--- a/inventory_provider/__init__.py
+++ b/inventory_provider/__init__.py
@@ -78,6 +78,9 @@ def create_app():
     from inventory_provider.routes import msr
     app.register_blueprint(msr.routes, url_prefix='/msr')
 
+    from inventory_provider.routes import lnetd
+    app.register_blueprint(lnetd.routes, url_prefix='/LnetD')
+
     if app.config.get('ENABLE_TESTING_ROUTES', False):
         from inventory_provider.routes import testing
         app.register_blueprint(testing.routes, url_prefix='/testing')
diff --git a/inventory_provider/routes/classifier.py b/inventory_provider/routes/classifier.py
index 9d72b5b89a79e1ec2e54e0fb247294a09a5c7ad5..f470dd3393c58b94fccec10a0597b388b5b16757 100644
--- a/inventory_provider/routes/classifier.py
+++ b/inventory_provider/routes/classifier.py
@@ -143,15 +143,13 @@ def get_interface_services_and_loc(ims_source_equipment, ims_interface, redis):
         for s in json.loads(raw_services.decode('utf-8')):
             related_services.update(
                 {r['id']: r for r in s['related-services']})
-            if s['monitored'] and s['circuit_type'] == 'service':
+            if s['circuit_type'] == 'service':
                 contacts.update(set(s.pop('contacts', set())))
                 _format_service(s)
                 result['services'].append(s)
         result['related-services'] = list(related_services.values())
         result['contacts'] = sorted(list(contacts))
 
-        # non-monitored related services are not added by the worker so don't
-        # need filtering out here
         if not result['services']:
             result.pop('services', None)
         if result['related-services']:
diff --git a/inventory_provider/routes/common.py b/inventory_provider/routes/common.py
index af20bf9a4b614897a362e6e26b90ad2d24909795..56ed526edd5f5fa87a6b0c37947f73bf36fb8f73 100644
--- a/inventory_provider/routes/common.py
+++ b/inventory_provider/routes/common.py
@@ -252,3 +252,17 @@ def load_json_docs(config_params, key_pattern, num_threads=10):
 def load_xml_docs(config_params, key_pattern, num_threads=10):
     yield from _load_redis_docs(
         config_params, key_pattern, num_threads, doc_type=_DECODE_TYPE_XML)
+
+
+def load_snmp_indexes(hostname=None):
+    result = dict()
+    key_pattern = f'snmp-interfaces:{hostname}*' \
+        if hostname else 'snmp-interfaces:*'
+
+    for doc in load_json_docs(
+            config_params=current_app.config['INVENTORY_PROVIDER_CONFIG'],
+            key_pattern=key_pattern):
+        router = doc['key'][len('snmp-interfaces:'):]
+        result[router] = {e['name']: e for e in doc['value']}
+
+    return result
diff --git a/inventory_provider/routes/lnetd.py b/inventory_provider/routes/lnetd.py
new file mode 100644
index 0000000000000000000000000000000000000000..0025ef218b40e08d322e72c62a947c798435bbe1
--- /dev/null
+++ b/inventory_provider/routes/lnetd.py
@@ -0,0 +1,164 @@
+import json
+import logging
+import re
+
+from flask import Blueprint, Response, current_app, request
+
+from inventory_provider import juniper
+from inventory_provider.routes import common
+from inventory_provider.routes.common import _ignore_cache_or_retrieve
+
+logger = logging.getLogger(__name__)
+routes = Blueprint('lnetd-support-routes', __name__)
+
+INTERFACE_LIST_SCHEMA = {
+    '$schema': 'http://json-schema.org/draft-07/schema#',
+
+    'definitions': {
+
+        'ipv4-interface-address': {
+            'type': 'string',
+            'pattern': r'^(\d+\.){3}\d+/\d+$'
+        },
+        'ipv6-interface-address': {
+            'type': 'string',
+            'pattern': r'^[a-f\d:]+/\d+$'
+        },
+
+        'interface': {
+            'type': 'object',
+            'properties': {
+                'hostname': {'type': 'string'},
+                'interface': {'type': 'string'},
+                'ifIndex': {'type': 'integer', 'minimum': 1},
+                'ipv4': {
+                    'type': 'array',
+                    'items': {'$ref': '#/definitions/ipv4-interface-address'}
+                },
+                'ipv6': {
+                    'type': 'array',
+                    'items': {'$ref': '#/definitions/ipv6-interface-address'}
+                },
+            },
+            'required': ['hostname', 'interface', 'ifIndex', 'ipv4', 'ipv6'],
+            'additionalProperties': False
+        },
+    },
+
+    'type': 'array',
+    'items': {'$ref': '#/definitions/interface'}
+}
+
+
+def _add_snmp_indexes(interfaces, hostname=None):
+    """
+    generator that adds snmp ifIndex to each interface, if available
+    (only interfaces with an snmp index are yielded)
+
+    :param interfaces: result of _load_interfaces
+    :param hostname: hostname or None for all
+    :return: generator that yields interfaces with 'ifIndex' added
+    """
+    snmp_indexes = common.load_snmp_indexes(hostname)
+    for ifc in interfaces:
+
+        hostname = ifc['hostname']
+        if hostname not in snmp_indexes:
+            continue
+
+        interface = ifc['interface']
+        if interface not in snmp_indexes[hostname]:
+            continue
+
+        ifc['ifIndex'] = snmp_indexes[hostname][interface]['index']
+        yield ifc
+
+
+def _load_router_interfaces(hostname):
+    """
+    loads basic interface data for production & lab routers
+
+    :param hostname:
+    :return:
+    """
+    def _load_docs(key_pattern):
+
+        m = re.match(r'^(.*netconf:).+', key_pattern)
+        assert m  # sanity
+        key_prefix_len = len(m.group(1))
+        assert key_prefix_len >= len('netconf:')  # sanity
+
+        for doc in common.load_xml_docs(
+                config_params=current_app.config['INVENTORY_PROVIDER_CONFIG'],
+                key_pattern=key_pattern,
+                num_threads=10):
+
+            router = doc['key'][key_prefix_len:]
+
+            for ifc in juniper.list_interfaces(doc['value']):
+                if not ifc['description']:
+                    continue
+                # seems we're only interested in interfaces with addresses?
+                if not ifc['ipv4'] and not ifc['ipv6']:
+                    continue
+
+                yield {
+                    'hostname': router,
+                    'interface': ifc['name'],
+                    'ipv4': ifc['ipv4'],
+                    'ipv6': ifc['ipv6']
+                }
+
+    base_key_pattern = f'netconf:{hostname}*' if hostname else 'netconf:*'
+    yield from _load_docs(base_key_pattern)
+    yield from _load_docs(f'lab:{base_key_pattern}')
+
+
+def _load_interfaces(hostname=None):
+    """
+    prepares the result of a call to /interfaces
+
+    :param hostname: hostname or None for all
+    :return: generator yielding interface elements
+    """
+    basic_interfaces = _load_router_interfaces(hostname)
+    return _add_snmp_indexes(basic_interfaces, hostname)
+
+
+@routes.route("/interfaces", methods=['GET', 'POST'])
+@routes.route('/interfaces/<hostname>', methods=['GET', 'POST'])
+@common.require_accepts_json
+def interfaces(hostname=None):
+    """
+    Handler for `/LnetD/interfaces` and
+    `/LnetD/interfaces/<hostname>`
+    which returns information for either all interfaces
+    or those on the requested hostname.
+
+    .. asjson::
+       inventory_provider.routes.lnetd.INTERFACE_LIST_SCHEMA
+
+    :param hostname: optional, if present should be a router hostname
+    :return:
+    """
+
+    cache_key = f'classifier-cache:lnetd-interfaces:{hostname}' \
+        if hostname else 'classifier-cache:lnetd-interfaces:all'
+
+    r = common.get_current_redis()
+
+    result = _ignore_cache_or_retrieve(request, cache_key, r)
+
+    if not result:
+        result = list(_load_interfaces(hostname))
+        if not result:
+            return Response(
+                response='no interfaces found',
+                status=404,
+                mimetype='text/html')
+
+        result = json.dumps(result)
+        # cache this data for the next call
+        r.set(cache_key, result.encode('utf-8'))
+
+    return Response(result, mimetype="application/json")
diff --git a/inventory_provider/routes/poller.py b/inventory_provider/routes/poller.py
index 73681162903e04ee84751946535c6a05e1c1526d..900fd11c08c439bab616242296fe4360b2c6f660 100644
--- a/inventory_provider/routes/poller.py
+++ b/inventory_provider/routes/poller.py
@@ -123,20 +123,6 @@ def after_request(resp):
     return common.after_request(resp)
 
 
-def _load_snmp_indexes(hostname=None):
-    result = dict()
-    key_pattern = f'snmp-interfaces:{hostname}*' \
-        if hostname else 'snmp-interfaces:*'
-
-    for doc in common.load_json_docs(
-            config_params=current_app.config['INVENTORY_PROVIDER_CONFIG'],
-            key_pattern=key_pattern):
-        router = doc['key'][len('snmp-interfaces:'):]
-        result[router] = {e['name']: e for e in doc['value']}
-
-    return result
-
-
 def _load_interface_bundles(hostname=None):
     result = dict()
 
@@ -295,7 +281,7 @@ def _add_snmp_indexes(interfaces, hostname=None):
     :param hostname: hostname or None for all
     :return: generator with 'snmp-index' optionally added to each element
     """
-    snmp_indexes = _load_snmp_indexes(hostname)
+    snmp_indexes = common.load_snmp_indexes(hostname)
     for ifc in interfaces:
         router_snmp = snmp_indexes.get(ifc['router'], None)
         if router_snmp and ifc['name'] in router_snmp:
diff --git a/inventory_provider/tasks/worker.py b/inventory_provider/tasks/worker.py
index 82644b92c8cdcd2ab1c9ebf7fe9c3b2eaa461a9e..a36a5faf2214f572c9f0eca9170fea3782f51e84 100644
--- a/inventory_provider/tasks/worker.py
+++ b/inventory_provider/tasks/worker.py
@@ -571,8 +571,6 @@ def update_circuit_hierarchy_and_port_id_services(self, use_current=False):
     tls_names = list(ims_data.get_service_types(ds1))
     customer_contacts = \
         {k: v for k, v in ims_data.get_customer_service_emails(ds1)}
-    circuit_ids_not_to_monitor = \
-        list(ims_data.get_non_monitored_circuit_ids(ds1))
     circuit_ids_to_monitor = \
         list(ims_data.get_monitored_circuit_ids(ds1))
 
@@ -618,7 +616,6 @@ def update_circuit_hierarchy_and_port_id_services(self, use_current=False):
         nonlocal hierarchy
         hierarchy = {}
         for d in ims_data.get_circuit_hierarchy(ds1):
-            d['monitored'] = d['id'] in circuit_ids_to_monitor
             d['contacts'] = customer_contacts.get(d['customerid'], [])
             hierarchy[d['id']] = d
         logger.debug("hierarchy complete")
@@ -667,25 +664,20 @@ def update_circuit_hierarchy_and_port_id_services(self, use_current=False):
         c = hierarchy.get(circuit_id, None)
         if c:
 
-            def _is_rs(candidate):
-                if candidate['id'] in circuit_ids_not_to_monitor:
-                    return False
-                if candidate['product'] in tls_names:
-                    return True
-                # if candidate['speed'] == 'BGP':
-                #     return True
-                return False
-
-            if _is_rs(c):
+            if c['circuit-type'] == 'service':
                 rs[c['id']] = {
                     'id': c['id'],
                     'name': c['name'],
-                    'status': c['status'],
-                    'circuit_type': 'service',
+                    'circuit_type': c['circuit-type'],
                     'service_type': c['product'],
                     'project': c['project'],
                     'contacts': sorted(list(c['contacts']))
                 }
+                if c['id'] in circuit_ids_to_monitor:
+                    rs[c['id']]['status'] = c['status']
+                else:
+                    rs[c['id']]['status'] = 'non-monitored'
+
             if c['sub-circuits']:
                 for sub in c['sub-circuits']:
                     temp_parents = \
@@ -694,10 +686,10 @@ def update_circuit_hierarchy_and_port_id_services(self, use_current=False):
         return list(rs.values())
 
     def _format_service(s):
-        if s['id'] in circuit_ids_not_to_monitor:
-            s['monitored'] = False
-        else:
-            s['monitored'] = True
+
+        if s['circuit_type'] == 'service' \
+                and s['id'] not in circuit_ids_to_monitor:
+            s['status'] = 'non-monitored'
         pd_a = port_id_details[s['port_a_id']][0]
         location_a = locations.get(pd_a['equipment_name'], None)
         if location_a:
@@ -758,10 +750,6 @@ def update_circuit_hierarchy_and_port_id_services(self, use_current=False):
                         'name': hierarchy[x]['name'],
                         'status': hierarchy[x]['status']
                     }
-                    if c['id'] in circuit_ids_not_to_monitor:
-                        c['monitored'] = False
-                    else:
-                        c['monitored'] = True
                     circ['fibre-routes'].append(c)
 
                 circ['related-services'] = \
diff --git a/setup.py b/setup.py
index 5ce7b85e79ccc418c5fb07e9e92e810a62b811fc..758d2bcd73c16d0e2032cdc1989c74adbfc3d1de 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
 
 setup(
     name='inventory-provider',
-    version="0.64",
+    version="0.65",
     author='GEANT',
     author_email='swd@geant.org',
     description='Dashboard inventory provider',
diff --git a/test/per_router/test_lnetd_routes.py b/test/per_router/test_lnetd_routes.py
new file mode 100644
index 0000000000000000000000000000000000000000..d4017fa36fc09107af505aa37bc81ed563e3df22
--- /dev/null
+++ b/test/per_router/test_lnetd_routes.py
@@ -0,0 +1,23 @@
+import json
+import jsonschema
+import pytest
+from inventory_provider.routes.lnetd import INTERFACE_LIST_SCHEMA
+
+DEFAULT_REQUEST_HEADERS = {
+    'Accept': ['application/json']
+}
+
+
+def test_router_interfaces(router, client):
+    if router.startswith('qfx'):
+        pytest.skip('no interfaces expected for {router}, skipping')
+
+    rv = client.post(
+        f'/LnetD/interfaces/{router}',
+        headers=DEFAULT_REQUEST_HEADERS)
+
+    assert rv.status_code == 200
+    response = json.loads(rv.data.decode('utf-8'))
+    jsonschema.validate(response, INTERFACE_LIST_SCHEMA)
+    assert response  # at least shouldn't be empty
+    assert all(ifc['hostname'] == router for ifc in response)
diff --git a/test/test_general_lnetd_routes.py b/test/test_general_lnetd_routes.py
new file mode 100644
index 0000000000000000000000000000000000000000..4cec6a5643046d37374b3ee6f1471ebe78e1233c
--- /dev/null
+++ b/test/test_general_lnetd_routes.py
@@ -0,0 +1,20 @@
+import json
+import jsonschema
+from inventory_provider.routes import lnetd
+
+DEFAULT_REQUEST_HEADERS = {
+    'Accept': ['application/json']
+}
+
+
+def test_get_all_interfaces(client):
+    rv = client.get(
+        '/LnetD/interfaces',
+        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, lnetd.INTERFACE_LIST_SCHEMA)
+    response_routers = {ifc['hostname'] for ifc in response_data}
+    assert len(response_routers) > 1, \
+        'there should data from be lots of routers'