diff --git a/docs/source/protocol.rst b/docs/source/protocol.rst
index dd878449d8d6d2a26178e837baf8e7c49454780c..ccbb59d5edff3d86b11f491d59a10d6b5e7f8be0 100644
--- a/docs/source/protocol.rst
+++ b/docs/source/protocol.rst
@@ -39,3 +39,5 @@ API modules
 .. automodule:: inventory_provider.routes.data
 
 .. automodule:: inventory_provider.routes.jobs
+
+.. automodule:: inventory_provider.routes.neteng
diff --git a/inventory_provider/__init__.py b/inventory_provider/__init__.py
index d350c9ebbcd98a251485f562b3259f9980ae8721..b3ab8f8a1f71ef03c5a0ca0f456de37578e38042 100644
--- a/inventory_provider/__init__.py
+++ b/inventory_provider/__init__.py
@@ -74,6 +74,9 @@ def create_app():
     from inventory_provider.routes import lnetd
     app.register_blueprint(lnetd.routes, url_prefix='/LnetD')
 
+    from inventory_provider.routes import neteng
+    app.register_blueprint(neteng.routes, url_prefix='/neteng')
+
     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/db/ims_data.py b/inventory_provider/db/ims_data.py
index f0f9ddb516115a1e0435b9b390a933832122c340..e3a59750d9cb1b30336eb95494ebc8b14f3b4b51 100644
--- a/inventory_provider/db/ims_data.py
+++ b/inventory_provider/db/ims_data.py
@@ -26,6 +26,40 @@ IMS_OPSDB_STATUS_MAP = {
 STATUSES_TO_IGNORE = \
     [InventoryStatus.OUT_OF_SERVICE.value]
 
+NODE_LOCATION_SCHEMA = {
+    '$schema': 'http://json-schema.org/draft-07/schema#',
+    'definitions': {
+        'pop-location': {
+            'type': 'object',
+            'properties': {
+                'name': {'type': 'string'},
+                'city': {'type': 'string'},
+                'country': {'type': 'string'},
+                'abbreviation': {'type': 'string'},
+                'longitude': {'type': 'number'},
+                'latitude': {'type': 'number'}
+            },
+            'required': [
+                'name',
+                'city',
+                'country',
+                'abbreviation',
+                'longitude',
+                'latitude'],
+            'additionalProperties': False
+        }
+    },
+
+    'type': 'object',
+    'properties': {
+        'equipment-name': {'type': 'string'},
+        'status': {'type': 'string'},
+        'pop':  {'$ref': '#/definitions/pop-location'}
+    },
+    'required': ['equipment-name', 'status', 'pop'],
+    'additionalProperties': False
+}
+
 
 def get_non_monitored_circuit_ids(ds: IMS):
     # note the id for the relevant field is hard-coded. I didn't want to use
@@ -300,6 +334,17 @@ def get_circuit_hierarchy(ds: IMS):
 
 
 def get_node_locations(ds: IMS):
+    """
+    return location info for all Site nodes
+
+    yields dictionaries formatted as:
+
+    .. as_json::
+        inventory_provider.db.ims_data.NODE_LOCATION_SCHEMA
+
+    :param ds:
+    :return: yields dicts as above
+    """
     site_nav_props = [
         ims.SITE_PROPERTIES['City'],
         ims.SITE_PROPERTIES['SiteAliases'],
diff --git a/inventory_provider/routes/neteng.py b/inventory_provider/routes/neteng.py
new file mode 100644
index 0000000000000000000000000000000000000000..29f8abcd9c963c06ac40fed5063d32f401435a28
--- /dev/null
+++ b/inventory_provider/routes/neteng.py
@@ -0,0 +1,68 @@
+"""
+Neteng Support Endpoints
+=========================
+
+These endpoints are intended for use by neteng tools.
+
+
+.. contents:: :local:
+
+/neteng/location/equipment-name
+---------------------------------
+
+.. autofunction:: inventory_provider.routes.neteng.get_location
+
+"""
+import json
+import logging
+import threading
+
+from flask import Blueprint, Response, jsonify
+
+from inventory_provider.routes import common
+
+routes = Blueprint('neteng-query-routes', __name__)
+logger = logging.getLogger(__name__)
+_subnet_lookup_semaphore = threading.Semaphore()
+
+
+@routes.after_request
+def after_request(resp):
+    return common.after_request(resp)
+
+
+@routes.route('/location/<equipment>', methods=['GET', 'POST'])
+@common.require_accepts_json
+def get_location(equipment):
+    """
+    Handler for `/neteng/location/equipment-name`
+
+    This method will pop location information for the IMS node
+    with name = `equipment-name`.
+
+    404 is returned if the IMS node name is not known.
+    Otherwise the return value will be formatted as:
+
+    .. asjson::
+        inventory_provider.db.ims_data.NODE_LOCATION_SCHEMA
+
+    :return: as above
+    """
+
+    r = common.get_current_redis()
+
+    value = r.get(f'ims:location:{equipment}')
+    if not value:
+        return Response(
+            response='no location information available for "{equipment}"',
+            status=404,
+            mimetype='text/html')
+
+    value = json.loads(value.decode('utf-8'))
+    if not value:
+        return Response(
+            response='unexpected empty cached data for "{equipment}"',
+            status=500,
+            mimetype='text/html')
+
+    return jsonify(value[0])
diff --git a/test/test_ims_data.py b/test/test_ims_data.py
index cd8d7e421eb8750f077bf74a22c8f6b3ee3d019c..7d07aff8e7b0ee6ef3a13801beae0a4cd3cd71a2 100644
--- a/test/test_ims_data.py
+++ b/test/test_ims_data.py
@@ -1,19 +1,30 @@
 import json
+import os
+
+import jsonschema
 
 import inventory_provider
 from inventory_provider.db.ims import InventoryStatus
 from inventory_provider.db.ims_data import lookup_lg_routers, \
     get_node_locations, IMS_OPSDB_STATUS_MAP, \
     get_port_id_services, get_port_details, \
-    get_circuit_hierarchy
+    get_circuit_hierarchy, NODE_LOCATION_SCHEMA
+
+
+def _json_test_data(filename):
+    abs_filename = os.path.join(
+        os.path.dirname(__file__),
+        'data',
+        filename)
+    with open(abs_filename) as data:
+        return json.load(data)
 
 
 def test_get_circuit_hierarchy(mocker):
 
     ds = inventory_provider.db.ims.IMS(
         'http://dummy_base', 'dummy_username', 'dummy_password')
-    with open('test/data/ims_circuit_hierarchy_data.json') as data:
-        se_data = json.load(data)
+    se_data = _json_test_data('ims_circuit_hierarchy_data.json')
     mocker.patch.object(
         inventory_provider.db.ims.IMS,
         'get_filtered_entities',
@@ -55,8 +66,7 @@ def test_get_circuit_hierarchy(mocker):
 def test_get_port_details(mocker):
 
     def _se(entity, y, step_count):
-        with open(f'test/data/ims_{entity}_details_data.json') as data:
-            return json.load(data)
+        return _json_test_data(f'ims_{entity}_details_data.json')
 
     mocker.patch.object(
         inventory_provider.db.ims.IMS,
@@ -105,8 +115,7 @@ def test_get_port_details(mocker):
 
 
 def test_get_port_id_services(mocker):
-    with open('test/data/ims_port_id_services_data.json') as data:
-        d = json.load(data)
+    d = _json_test_data('ims_port_id_services_data.json')
 
     mocker.patch.object(
         inventory_provider.db.ims.IMS,
@@ -215,8 +224,8 @@ def test_get_port_id_services(mocker):
 def test_lookup_lg_routers(mocker):
 
     ims = mocker.patch('inventory_provider.db.ims.IMS')
-    with open('test/data/ims_lg_data.json') as data:
-        ims.return_value.get_filtered_entities.return_value = json.load(data)
+    ims.return_value.get_filtered_entities.return_value \
+        = _json_test_data('ims_lg_data.json')
     ims.return_value.get_entity_by_id.return_value = {
         'name': 'pop name',
         'longitude': 'long',
@@ -266,13 +275,16 @@ def test_lookup_lg_routers(mocker):
 
 def test_get_node_location(mocker):
     ims = mocker.patch('inventory_provider.db.ims.IMS')
-    with open('test/data/ims_nodes_data.json') as data:
-        resp_data = json.load(data)
+    resp_data = _json_test_data('ims_nodes_data.json')
     ims.return_value.get_all_entities.return_value = resp_data
 
     ds = inventory_provider.db.ims.IMS(
         'dummy_base', 'dummy_username', 'dummy_password')
     res = list(get_node_locations(ds))
+    for name, node in res:
+        assert isinstance(name, str)
+        jsonschema.validate(node, NODE_LOCATION_SCHEMA)
+
     assert len(res) == 36
     assert res[0] == ('LON3_CX_01', {
         'equipment-name': 'LON3_CX_01',
diff --git a/test/test_neteng_routes.py b/test/test_neteng_routes.py
new file mode 100644
index 0000000000000000000000000000000000000000..1e95322a3be4cd43a83caeea12c2a9f6d35e034e
--- /dev/null
+++ b/test/test_neteng_routes.py
@@ -0,0 +1,27 @@
+import json
+import jsonschema
+import pytest
+
+from inventory_provider.db.ims_data import NODE_LOCATION_SCHEMA
+
+
+@pytest.mark.parametrize('equipment_name', [
+    'MX1.AMS.NL',
+    'MIL-OLA1',
+    'LON02-GRV1'
+])
+def test_location(client, mocked_redis, equipment_name):
+    rv = client.post(
+        f'/neteng/location/{equipment_name}',
+        headers={'Accept': ['application/json']})
+    assert rv.status_code == 200
+    jsonschema.validate(
+        json.loads(rv.data.decode('utf-8')),
+        NODE_LOCATION_SCHEMA)
+
+
+def test_location_not_found(client, mocked_redis):
+    rv = client.post(
+        '/neteng/location/BOGUS.EQUIPMENT.NAME',
+        headers={'Accept': ['application/json']})
+    assert rv.status_code == 404