diff --git a/README.md b/README.md
index 1f9360ce53b68f77c88348a50dc26a6c876bc512..142aacf6f32c6ee28f219f19618b3e77bdb31972 100644
--- a/README.md
+++ b/README.md
@@ -252,3 +252,7 @@ Any non-empty responses are JSON formatted messages.
 
   This resource updates the inventory network data for juniper devices.
 
+* /jobs/update-startup
+
+  This resource updates data that should only be refreshed
+  in case of system restart.
diff --git a/changelog b/changelog
index 52a582c38c54e861fbbf23e7e8a3d3e50d457429..73a599d671ad205bf199e5b7a6cb2257f87fe409 100644
--- a/changelog
+++ b/changelog
@@ -14,3 +14,6 @@
 0.12: added addresses to interface response
       put actual module number in version response
 0.13: added external inventory caching
+0.14: added sample route for startup-only tasks
+      added method for caching selected alarmsdb tables
+      added caching of last known interface status from alarmsdb
diff --git a/inventory_provider/alarmsdb.py b/inventory_provider/alarmsdb.py
index b3256e10bb1b60c3511d6942dff4f061ecd75ed1..7eb2cba54356fffcf55ae51151e9d0e78c03210d 100644
--- a/inventory_provider/alarmsdb.py
+++ b/inventory_provider/alarmsdb.py
@@ -1,15 +1,14 @@
 from inventory_provider import db
 
 
-def get_last_known_infinera_interface_status(connection, equipment, interface):
+def get_last_known_infinera_interface_status(crs, equipment, interface):
     query = "SELECT status FROM infinera_alarms" \
             " WHERE" \
             " CONCAT(ne_name, '-', REPLACE(object_name, 'T', '')) = %s" \
             " ORDER BY ne_init_time DESC, ne_clear_time DESC LIMIT 1"
     search_string = equipment + "-" + interface
-    with db.cursor(connection) as crs:
-        crs.execute(query, (search_string,))
-        result = crs.fetchone()
+    crs.execute(query, (search_string,))
+    result = crs.fetchone()
     if not result:
         return "unknown"
     elif result[0] == "Raised":
@@ -18,13 +17,12 @@ def get_last_known_infinera_interface_status(connection, equipment, interface):
         return "up"
 
 
-def get_last_known_coriant_interface_status(connection, equipment, interface):
+def get_last_known_coriant_interface_status(crs, equipment, interface):
     query = "SELECT status FROM coriant_alarms" \
             " WHERE ne_id_name = %s AND entity_string LIKE %s" \
             " ORDER BY last_event_time DESC LIMIT 1"
-    with db.cursor(connection) as crs:
-        crs.execute(query, (equipment, interface + "-%"))
-        result = crs.fetchone()
+    crs.execute(query, (equipment, interface + "-%"))
+    result = crs.fetchone()
     if not result:
         return "unknown"
     elif result[0] == "Raised":
@@ -34,14 +32,13 @@ def get_last_known_coriant_interface_status(connection, equipment, interface):
 
 
 def get_last_known_juniper_link_interface_status(
-        connection, equipment, interface):
+        crs, equipment, interface):
     query = "SELECT IF(link_admin_status = 'up'" \
             " AND link_oper_status = 'up', 1, 0) AS up FROM juniper_alarms" \
             " WHERE equipment_name = %s AND link_interface_name = %s" \
             " ORDER BY alarm_id DESC LIMIT 1"
-    with db.cursor(connection) as crs:
-        crs.execute(query, ('lo0.' + equipment, interface))
-        result = crs.fetchone()
+    crs.execute(query, ('lo0.' + equipment, interface))
+    result = crs.fetchone()
     if not result:
         return "unknown"
     elif result[0] == 0:
@@ -50,13 +47,27 @@ def get_last_known_juniper_link_interface_status(
         return "up"
 
 
-def get_last_known_interface_status(connection, equipment, interface):
+def get_last_known_interface_status(crs, equipment, interface):
     result = get_last_known_infinera_interface_status(
-        connection, equipment, interface)
+        crs, equipment, interface)
     if result == "unknown":
         result = get_last_known_coriant_interface_status(
-            connection, equipment, interface)
+            crs, equipment, interface)
     if result == "unknown":
         result = get_last_known_juniper_link_interface_status(
-            connection, equipment, interface)
+            crs, equipment, interface)
     return result
+
+
+def _load_juniper_servers_table(connection):
+    with db.cursor(connection) as crs:
+        crs.execute('select ip_address, project_name from juniper_servers')
+        for row in crs.fetchall():
+            yield {
+                'ip_address': row[0],
+                'project_name': row[1]
+            }
+
+
+def load_cache(connection):
+    yield "juniper_servers", list(_load_juniper_servers_table(connection))
diff --git a/inventory_provider/routes/data.py b/inventory_provider/routes/data.py
index 3cc9e58fcda1a6dce3ebfd509204347f369895df..30810e45b074fbf599e5d26ace622675e7e7ca53 100644
--- a/inventory_provider/routes/data.py
+++ b/inventory_provider/routes/data.py
@@ -2,11 +2,12 @@ import functools
 import json
 import pkg_resources
 
-from flask import Blueprint, request, Response, current_app
+from flask import Blueprint, jsonify, request, Response, current_app
 from lxml import etree
 import redis
 
-from inventory_provider import juniper
+from inventory_provider import db, juniper
+from inventory_provider.storage import external_inventory
 
 routes = Blueprint("inventory-data-query-routes", __name__)
 
@@ -162,3 +163,18 @@ def bgp_configs(hostname):
     return Response(
         json.dumps(routes),
         mimetype="application/json")
+
+
+@routes.route("/interfaces/status/<hostname>/<path:interface>",
+              methods=['GET', 'POST'])
+@require_accepts_json
+def interface_statuses(hostname, interface):
+    r = db.get_redis()
+    result = r.hget(external_inventory.interface_status_key,
+                    "{}::{}".format(hostname, interface))
+    if not result:
+        return Response(
+            response="no available info for {} {}".format(hostname, interface),
+            status=404,
+            mimetype="text/html")
+    return jsonify({"status": result.decode('utf-8')})
diff --git a/inventory_provider/routes/jobs.py b/inventory_provider/routes/jobs.py
index b1a78c86065bc0a9446266c88aecd2a4e747f1f2..315c3e8d7c960228304a6a5020497cde0bbe1f42 100644
--- a/inventory_provider/routes/jobs.py
+++ b/inventory_provider/routes/jobs.py
@@ -38,6 +38,17 @@ def update():
     return Response("OK")
 
 
+@routes.route("/update-startup", methods=['GET', 'POST'])
+def startup_update():
+    task_logger = logging.getLogger(TASK_LOGGER_NAME)
+    task_logger.debug(
+            'launching task: '
+            'inventory_provider.tasks.worker.update_alarmsdb_cache')
+    app.send_task(
+        'inventory_provider.tasks.worker.update_alarmsdb_cache')
+    return Response("OK")
+
+
 @routes.route("update-services", methods=['GET'])
 def update_service():
     config = current_app.config['INVENTORY_PROVIDER_CONFIG']
diff --git a/inventory_provider/storage/external_inventory.py b/inventory_provider/storage/external_inventory.py
index cc7bccf69f48c717202e22656f88f9f55f5c33aa..e721a348d52a83ca82121dd299f674f630d38392 100644
--- a/inventory_provider/storage/external_inventory.py
+++ b/inventory_provider/storage/external_inventory.py
@@ -1,6 +1,7 @@
 import json
 from collections import defaultdict
-from inventory_provider import db
+from flask import current_app
+from inventory_provider import alarmsdb, db
 
 
 services_key = "inv_services"
@@ -8,27 +9,38 @@ interfaces_key = "inv_interfaces"
 equipment_locations_key = "inv_eq_locations"
 service_child_to_parents_key = "inv_service_child_to_parents"
 service_parent_to_children_key = "inv_service_parent_to_children"
+interface_status_key = "service_status"
 
 
 def update_services_to_monitor(services):
     r = db.get_redis()
-    relevant_types = ('path', 'service', 'l2circuit')
+    relevant_types = ("path", "service", "l2circuit")
     r.delete(services_key)
     for service in services:
-        if service['circuit_type'].lower() in relevant_types:
-            r.hset(services_key, service['id'], json.dumps(service))
+        if service["circuit_type"].lower() in relevant_types:
+            r.hset(services_key, service["id"], json.dumps(service))
 
 
 def update_interfaces_to_services(services):
     r = db.get_redis()
-    mapped_interfaces = defaultdict(list)
-    r.delete(interfaces_key)
-    for service in services:
-        key = "{}::{}".format(
-            service['equipment'],
-            service['interface_name']
-        )
-        mapped_interfaces[key].append(service)
+    config = current_app.config["INVENTORY_PROVIDER_CONFIG"]
+    with db.connection(config["alarms-db"]) as cnx:
+        with db.cursor(cnx) as csr:
+            mapped_interfaces = defaultdict(list)
+            r.delete(interfaces_key)
+            for service in services:
+                key = "{}::{}".format(
+                    service["equipment"],
+                    service["interface_name"]
+                )
+                mapped_interfaces[key].append(service)
+                if not r.hexists(interface_status_key, key):
+                    r.hset(interface_status_key, key,
+                           alarmsdb.get_last_known_interface_status(
+                               csr,
+                               service["equipment"],
+                               service["interface_name"]
+                           ))
 
     for key, value in mapped_interfaces.items():
         r.hset(interfaces_key, key, json.dumps(value))
@@ -57,4 +69,4 @@ def update_equipment_locations(equipment_location_data):
     r = db.get_redis()
     r.delete(equipment_locations_key)
     for ld in equipment_location_data:
-        r.hset(equipment_locations_key, ld['equipment_name'], json.dumps(ld))
+        r.hset(equipment_locations_key, ld["equipment_name"], json.dumps(ld))
diff --git a/inventory_provider/tasks/worker.py b/inventory_provider/tasks/worker.py
index 78f8ef788dd3b1f9a50b5b8c9e41126d24626fec..8f2efb84f0badfac81a87851d541f9297c6a1bd0 100644
--- a/inventory_provider/tasks/worker.py
+++ b/inventory_provider/tasks/worker.py
@@ -6,8 +6,10 @@ import redis
 from lxml import etree
 
 from inventory_provider.tasks.app import app
+from inventory_provider import alarmsdb
 from inventory_provider import config
 from inventory_provider import constants
+from inventory_provider import db
 from inventory_provider import environment
 from inventory_provider import snmp
 from inventory_provider import juniper
@@ -38,6 +40,25 @@ class InventoryTask(Task):
             "saved %s, key %s" % (hostname, key))
         return "OK"
 
+    @staticmethod
+    def save_value(key, value):
+        assert isinstance(value, str), \
+            "sanity failure: expected string data as value"
+        r = redis.StrictRedis(
+            host=InventoryTask.config["redis"]["hostname"],
+            port=InventoryTask.config["redis"]["port"])
+        r.set(
+            name=key,
+            value=value)
+        InventoryTask.logger.debug("saved %s" % key)
+        return "OK"
+
+    @staticmethod
+    def save_value_json(key, data_obj):
+        InventoryTask.save_value(
+            key,
+            json.dumps(data_obj))
+
     @staticmethod
     def save_key_json(hostname, key, data_obj):
         InventoryTask.save_key(
@@ -102,3 +123,15 @@ def netconf_refresh_config(self, hostname):
         juniper.load_config(hostname, InventoryTask.config["ssh"]))
 
     logger.debug('FINISHED: netconf_refresh_config(%r)' % hostname)
+
+
+@app.task(bind=InventoryTask)
+def update_alarmsdb_cache(self):
+    logger = logging.getLogger(constants.TASK_LOGGER_NAME)
+    logger.debug('STARTING: update_alarmsdb_cache')
+
+    with db.connection(InventoryTask.config["alarms-db"]) as cx:
+        for table_name, data in alarmsdb.load_cache(cx):
+            InventoryTask.save_value_json('alarmsdb:%s' % table_name, data)
+
+    logger.debug('FINISHED: update_alarmsdb_cache')
diff --git a/setup.py b/setup.py
index b041145b677577119497968f883a6ed70f59cb09..d48ac903b5b8d269c01cbe3d2f8b600d35383380 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
 
 setup(
     name='inventory-provider',
-    version="0.13",
+    version="0.14",
     author='GEANT',
     author_email='swd@geant.org',
     description='Dashboard inventory provider',
diff --git a/test/storage/test_external_inventory.py b/test/storage/test_external_inventory.py
index 30c13e0bc7287fa2b118b875b05b707482028857..b0caf9684824c9a701e67a05f25ceca5468723ea 100644
--- a/test/storage/test_external_inventory.py
+++ b/test/storage/test_external_inventory.py
@@ -34,6 +34,12 @@ def test_update_services_to_monitor(mocker):
 def test_update_interfaces_to_services(mocker):
     mocked_redis = mocker.patch(
         "inventory_provider.storage.external_inventory.db.get_redis")
+    mocker.patch(
+        "inventory_provider.storage.external_inventory.current_app")
+    mocker.patch(
+        "inventory_provider.storage.external_inventory.db.connection")
+    mocker.patch(
+        "inventory_provider.storage.external_inventory.db.cursor")
     mocked_hset = mocked_redis.return_value.hset
     services = [
         {"equipment": "eq_0", "interface_name": "if_0"},
diff --git a/test/test_alarmsdb.py b/test/test_alarmsdb.py
index 7eb707c1210bca0a21cd36ebdc3b658290cca426..7236721fc5f43d4931214a98a8d610049429c8a1 100644
--- a/test/test_alarmsdb.py
+++ b/test/test_alarmsdb.py
@@ -2,27 +2,23 @@ import inventory_provider.alarmsdb as alarmsdb
 
 
 def test_infinera_interface_status(mocker):
-    mocked_get_cursor = mocker.patch('inventory_provider.alarmsdb.db.cursor')
-    mocked_execute = mocked_get_cursor. \
-        return_value.__enter__.return_value.execute
-    mocked_fetchone = mocked_get_cursor.return_value.__enter__. \
-        return_value.fetchone
-    mocked_fetchone.return_value = ('Raised',)
+    mock = mocker.Mock()
+    mock.fetchone.return_value = ('Raised',)
     assert alarmsdb.get_last_known_infinera_interface_status(
-        None, 'eq1', 'intfc1'
+        mock, 'eq1', 'intfc1'
     ) == "down"
 
-    mocked_fetchone.return_value = ("Clear",)
+    mock.fetchone.return_value = ("Clear",)
     assert alarmsdb.get_last_known_infinera_interface_status(
-            None, 'eq1', 'intfc1'
+        mock, 'eq1', 'intfc1'
     ) == "up"
 
-    mocked_fetchone.return_value = ()
+    mock.fetchone.return_value = ()
     assert alarmsdb.get_last_known_infinera_interface_status(
-            None, 'eq1', 'intfc1'
+        mock, 'eq1', 'intfc1'
     ) == "unknown"
 
-    mocked_execute.assert_called_with(
+    mock.execute.assert_called_with(
         "SELECT status FROM infinera_alarms WHERE"
         " CONCAT(ne_name, '-', REPLACE(object_name, 'T', '')) = %s"
         " ORDER BY ne_init_time DESC, ne_clear_time DESC LIMIT 1",
@@ -30,27 +26,23 @@ def test_infinera_interface_status(mocker):
 
 
 def test_coriant_interface_status(mocker):
-    mocked_get_cursor = mocker.patch('inventory_provider.alarmsdb.db.cursor')
-    mocked_execute = mocked_get_cursor. \
-        return_value.__enter__.return_value.execute
-    mocked_fetchone = mocked_get_cursor.return_value.__enter__. \
-        return_value.fetchone
-    mocked_fetchone.return_value = ('Raised',)
+    mock = mocker.Mock()
+    mock.fetchone.return_value = ('Raised',)
     assert alarmsdb.get_last_known_coriant_interface_status(
-        None, 'eq1', 'intfc1'
+        mock, 'eq1', 'intfc1'
     ) == "down"
 
-    mocked_fetchone.return_value = ("Clear",)
+    mock.fetchone.return_value = ("Clear",)
     assert alarmsdb.get_last_known_coriant_interface_status(
-            None, 'eq1', 'intfc1'
+        mock, 'eq1', 'intfc1'
     ) == "up"
 
-    mocked_fetchone.return_value = ()
+    mock.fetchone.return_value = ()
     assert alarmsdb.get_last_known_coriant_interface_status(
-            None, 'eq1', 'intfc1'
+        mock, 'eq1', 'intfc1'
     ) == "unknown"
 
-    mocked_execute.assert_called_with(
+    mock.execute.assert_called_with(
         "SELECT status FROM coriant_alarms"
         " WHERE ne_id_name = %s AND entity_string LIKE %s"
         " ORDER BY last_event_time DESC LIMIT 1",
@@ -58,27 +50,23 @@ def test_coriant_interface_status(mocker):
 
 
 def test_juniper_interface_status(mocker):
-    mocked_get_cursor = mocker.patch('inventory_provider.alarmsdb.db.cursor')
-    mocked_execute = mocked_get_cursor. \
-        return_value.__enter__.return_value.execute
-    mocked_fetchone = mocked_get_cursor.return_value.__enter__. \
-        return_value.fetchone
-    mocked_fetchone.return_value = (0,)
+    mock = mocker.Mock()
+    mock.fetchone.return_value = (0,)
     assert alarmsdb.get_last_known_juniper_link_interface_status(
-        None, 'eq1', 'intfc1'
+        mock, 'eq1', 'intfc1'
     ) == "down"
 
-    mocked_fetchone.return_value = (1,)
+    mock.fetchone.return_value = (1,)
     assert alarmsdb.get_last_known_juniper_link_interface_status(
-            None, 'eq1', 'intfc1'
+        mock, 'eq1', 'intfc1'
     ) == "up"
 
-    mocked_fetchone.return_value = ()
+    mock.fetchone.return_value = ()
     assert alarmsdb.get_last_known_juniper_link_interface_status(
-            None, 'eq1', 'intfc1'
+        mock, 'eq1', 'intfc1'
     ) == "unknown"
 
-    mocked_execute.assert_called_with(
+    mock.execute.assert_called_with(
                 "SELECT IF(link_admin_status = 'up'"
                 " AND link_oper_status = 'up', 1, 0)"
                 " AS up FROM juniper_alarms"