diff --git a/docs/source/protocol/poller.rst b/docs/source/protocol/poller.rst
index 8bfa6ddc4bb39ece105e3ba0adf93072a22e8355..64628fc062a190b28a0c4c27a55ac6db2b87b760 100644
--- a/docs/source/protocol/poller.rst
+++ b/docs/source/protocol/poller.rst
@@ -6,7 +6,15 @@ BRIAN support Endpoints
 
 These endpoints are intended for use by BRIAN.
 
-/poller/interfaces
+.. contents:: :local:
+
+/poller/interfaces</hostname>
 ---------------------------------
 
 .. autofunction:: inventory_provider.routes.poller.interfaces
+
+
+/poller/speeds</hostname>
+---------------------------------
+
+.. autofunction:: inventory_provider.routes.poller.interface_speeds
diff --git a/inventory_provider/routes/poller.py b/inventory_provider/routes/poller.py
index f606759e3d0bd09128270ba756c27946a8683b63..4bd254f76c827777abb661d51b7342dc48fa3031 100644
--- a/inventory_provider/routes/poller.py
+++ b/inventory_provider/routes/poller.py
@@ -9,6 +9,8 @@ from inventory_provider.routes import common
 logger = logging.getLogger(__name__)
 routes = Blueprint('poller-support-routes', __name__)
 
+Gb = 1 << 30
+
 INTERFACE_LIST_SCHEMA = {
     '$schema': 'http://json-schema.org/draft-07/schema#',
 
@@ -59,6 +61,26 @@ INTERFACE_LIST_SCHEMA = {
     'items': {'$ref': '#/definitions/interface'}
 }
 
+INTERFACE_SPEED_LIST_SCHEMA = {
+    '$schema': 'http://json-schema.org/draft-07/schema#',
+
+    'definitions': {
+        'interface': {
+            'type': 'object',
+            'properties': {
+                'router': {'type': 'string'},
+                'name': {'type': 'string'},
+                'speed': {'type': 'integer'}
+            },
+            'required': ['router', 'name', 'speed'],
+            'additionalProperties': False
+        },
+    },
+
+    'type': 'array',
+    'items': {'$ref': '#/definitions/interface'}
+}
+
 
 @routes.after_request
 def after_request(resp):
@@ -150,42 +172,83 @@ def _load_interfaces(hostname):
                 'name': ifc['name'],
                 'bundle': ifc['bundle'],
                 'bundle-parents': [],
-                'snmp-index': -1,
                 'description': ifc['description'],
                 'circuits': []
             }
 
 
-def _load_poller_interfaces(hostname=None):
+def _add_bundle_parents(interfaces, hostname=None):
+    """
+    generator that adds bundle-parents info to each interface.
 
-    snmp_indexes = _load_snmp_indexes(hostname)
+    :param interfaces: result of _load_interfaces
+    :param hostname: hostname or None for all
+    :return: generator with bundle-parents populated in each element
+    """
     bundles = _load_interface_bundles(hostname)
-    services = _load_services(hostname)
-
-    for ifc in _load_interfaces(hostname):
-
-        router_snmp = snmp_indexes.get(ifc['router'], None)
-        if not router_snmp or ifc['name'] not in router_snmp:
-            # there's no way to poll this interface
-            continue
-        ifc['snmp-index'] = router_snmp[ifc['name']]['index']
-        # TODO: uncomment this when it won't break poller-admin-service
-        # not urgent ... it looks empirically like all logical-system
-        #                interfaces are repeated for both communities
-        # ifc['snmp-community'] = router_snmp[ifc['name']]['community']
-
+    for ifc in interfaces:
         router_bundle = bundles.get(ifc['router'], None)
         if router_bundle:
             base_ifc = ifc['name'].split('.')[0]
             ifc['bundle-parents'] = router_bundle.get(base_ifc, [])
+        yield ifc
+
+
+def _add_circuits(interfaces, hostname=None):
+    """
+    generator that adds service info to each interface.
 
+    :param interfaces: result of _load_interfaces
+    :param hostname: hostname or None for all
+    :return: generator with 'circuits' populated in each element, if present
+    """
+    services = _load_services(hostname)
+    for ifc in interfaces:
         router_services = services.get(ifc['router'], None)
         if router_services:
             ifc['circuits'] = router_services.get(ifc['name'], [])
+        yield ifc
+
+
+def _add_snmp_indexes(interfaces, hostname=None):
+    """
+    generator that adds snmp info to each interface, if available
 
+    :param interfaces: result of _load_interfaces
+    :param hostname: hostname or None for all
+    :return: generator with 'snmp-index' optionally added to each element
+    """
+    snmp_indexes = _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:
+            ifc['snmp-index'] = router_snmp[ifc['name']]['index']
+            # TODO: uncomment this when it won't break poller-admin-service
+            # not urgent ... it looks empirically like all logical-system
+            #                interfaces are repeated for both communities
+            # ifc['snmp-community'] = router_snmp[ifc['name']]['community']
         yield ifc
 
 
+def _load_interfaces_to_poll(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_interfaces(hostname)
+    with_bundles = _add_bundle_parents(basic_interfaces, hostname)
+    with_circuits = _add_circuits(with_bundles, hostname)
+    with_snmp = _add_snmp_indexes(with_circuits, hostname)
+
+    def _has_snmp_index(ifc):
+        return 'snmp-index' in ifc
+
+    # only return interfaces that can be polled
+    return filter(_has_snmp_index, with_snmp)
+
+
 @routes.route("/interfaces", methods=['GET', 'POST'])
 @routes.route('/interfaces/<hostname>', methods=['GET', 'POST'])
 @common.require_accepts_json
@@ -216,7 +279,104 @@ def interfaces(hostname=None):
     if result:
         result = result.decode('utf-8')
     else:
-        result = list(_load_poller_interfaces(hostname))
+        result = list(_load_interfaces_to_poll(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")
+
+
+def interface_speed(ifc):
+    """
+    Return the maximum bits per second expected for the given interface.
+
+    cf. https://www.juniper.net/documentation/us/en/software/
+               vmx/vmx-getting-started/topics/task/
+               vmx-chassis-interface-type-configuring.html
+
+    :param ifc:
+    :return: an integer bits per second
+    """
+
+    def _name_to_speed(ifc_name):
+        if ifc_name.startswith('ge'):
+            return Gb
+        if ifc_name.startswith('xe'):
+            return 10 * Gb
+        if ifc_name.startswith('et'):
+            return 100 * Gb
+        logger.warning(f'unrecognized interface name: {ifc_name}')
+        return -1
+
+    if ifc['bundle-parents']:
+        if not ifc['name'].startswith('ae'):
+            logger.warning(
+                f'ifc has bundle-parents, but name is {ifc["name"]}')
+        return sum(_name_to_speed(name) for name in ifc['bundle-parents'])
+
+    return _name_to_speed(ifc['name'])
+
+
+def _load_interfaces_and_speeds(hostname=None):
+    """
+    prepares the result of a call to /speeds
+
+    :param hostname: hostname or None for all
+    :return: generator yielding interface elements
+    """
+    basic_interfaces = _load_interfaces(hostname)
+    with_bundles = _add_bundle_parents(basic_interfaces, hostname)
+    with_bundles = list(with_bundles)
+
+    def _result_ifc(ifc):
+        return {
+            'router': ifc['router'],
+            'name': ifc['name'],
+            'speed': interface_speed(ifc)
+        }
+
+    return map(_result_ifc, with_bundles)
+
+
+@routes.route("/speeds", methods=['GET', 'POST'])
+@routes.route('/speeds/<hostname>', methods=['GET', 'POST'])
+@common.require_accepts_json
+def interface_speeds(hostname=None):
+    """
+    Handler for `/poller/speeds` and
+    `/poller/speeds/<hostname>`
+    which returns information for either all interfaces
+    or those on the requested hostname.
+
+    The response is a list of maximum speed information (in bits
+    per second) for all known interfaces.
+
+    *speed <= 0 means the max interface speed can't be determined*
+
+    .. asjson::
+       inventory_provider.routes.poller.INTERFACE_SPEED_LIST_SCHEMA
+
+    :param hostname: optional, if present should be a router hostname
+    :return:
+    """
+
+    cache_key = f'classifier-cache:poller-interface-speeds:{hostname}' \
+        if hostname else 'classifier-cache:poller-interface-speeds:all'
+
+    r = common.get_current_redis()
+
+    result = r.get(cache_key)
+    if result:
+        result = result.decode('utf-8')
+    else:
+        result = list(_load_interfaces_and_speeds(hostname))
         if not result:
             return Response(
                 response='no interfaces found',
diff --git a/test/per_router/test_poller_routes.py b/test/per_router/test_poller_routes.py
index d7351c09dd899ab785cf73568a2039c8c96a0d10..bc304e5b7aca1fca9134dfceeba2c65e4de8d770 100644
--- a/test/per_router/test_poller_routes.py
+++ b/test/per_router/test_poller_routes.py
@@ -1,6 +1,7 @@
 import json
 import jsonschema
-from inventory_provider.routes.poller import INTERFACE_LIST_SCHEMA
+from inventory_provider.routes.poller \
+    import INTERFACE_LIST_SCHEMA, INTERFACE_SPEED_LIST_SCHEMA
 
 DEFAULT_REQUEST_HEADERS = {
     "Content-type": "application/json",
@@ -19,3 +20,16 @@ def test_router_interfaces(router, client):
     assert response  # at least shouldn't be empty
     response_routers = {ifc['router'] for ifc in response}
     assert response_routers == {router}
+
+
+def test_router_interface_speeds(router, client):
+    rv = client.post(
+        f'/poller/speeds/{router}',
+        headers=DEFAULT_REQUEST_HEADERS)
+
+    assert rv.status_code == 200
+    response = json.loads(rv.data.decode("utf-8"))
+    jsonschema.validate(response, INTERFACE_SPEED_LIST_SCHEMA)
+    assert response  # at least shouldn't be empty
+    response_routers = {ifc['router'] for ifc in response}
+    assert response_routers == {router}