diff --git a/changelog b/changelog
index 6fe9ef386da6a0410c22b0f11afc08cf79039948..fafaca0c0a61d2306743a7d6c708d20bbb36375c 100644
--- a/changelog
+++ b/changelog
@@ -1,2 +1,3 @@
 0.1: initial skeleton
-0.2: use celery for task management
\ No newline at end of file
+0.2: use celery for task management
+0.3: basic opsdb, alarmsdb coms & test api
\ No newline at end of file
diff --git a/inventory_provider/__init__.py b/inventory_provider/__init__.py
index 3eb4592480884d54a4bde47e961fea4ec900c2e3..a8e55e8edb0d75ffbbbcae07669b9d63450a064f 100644
--- a/inventory_provider/__init__.py
+++ b/inventory_provider/__init__.py
@@ -17,11 +17,17 @@ def create_app():
     app = Flask(__name__)
     app.secret_key = "super secret session key"
 
-    from inventory_provider import data_routes
-    app.register_blueprint(data_routes.routes, url_prefix='/data')
+    from inventory_provider.routes import data
+    app.register_blueprint(data.routes, url_prefix='/data')
 
-    from inventory_provider import job_routes
-    app.register_blueprint(job_routes.routes, url_prefix='/jobs')
+    from inventory_provider.routes import jobs
+    app.register_blueprint(jobs.routes, url_prefix='/jobs')
+
+    from inventory_provider.routes import opsdb
+    app.register_blueprint(opsdb.routes, url_prefix='/opsdb')
+
+    from inventory_provider.routes import alarmsdb
+    app.register_blueprint(alarmsdb.routes, url_prefix='/alarmsdb')
 
     if "SETTINGS_FILENAME" not in os.environ:
         assert False, \
diff --git a/inventory_provider/alarmsdb.py b/inventory_provider/alarmsdb.py
index 01a4390b2ba53bf112185a508da8b760365ef42b..c3e7d5bb57d843e1134a0a7c9bb2e7eb61bf4092 100644
--- a/inventory_provider/alarmsdb.py
+++ b/inventory_provider/alarmsdb.py
@@ -40,3 +40,4 @@ def _db_test(db, router):
         crs.execute(query, (router['hostname'],))
         for (absid,) in crs:
             database_logger.debug("absid: %r" % absid)
+            yield absid
diff --git a/inventory_provider/config.py b/inventory_provider/config.py
index 36d814633db7b53fdc9038b5c373f173bf4e6326..101f60e3688b37bdbee0eeeb1d2f55844bd736dc 100644
--- a/inventory_provider/config.py
+++ b/inventory_provider/config.py
@@ -6,9 +6,9 @@ import jsonschema
 
 CONFIG_SCHEMA = {
     "$schema": "http://json-schema.org/draft-07/schema#",
-    "type": "object",
-    "properties": {
-        "alarms-db": {
+
+    "definitions": {
+        "database_credentials": {
             "type": "object",
             "properties": {
                 "hostname": {"type": "string"},
@@ -18,7 +18,14 @@ CONFIG_SCHEMA = {
             },
             "required": ["hostname", "dbname", "username", "password"],
             "additionalProperties": False
-        },
+
+        }
+    },
+
+    "type": "object",
+    "properties": {
+        "alarms-db": {"$ref": "#/definitions/database_credentials"},
+        "ops-db": {"$ref": "#/definitions/database_credentials"},
         "oid_list.conf": {"type": "string"},
         "routers_community.conf": {"type": "string"},
         "ssh": {
@@ -42,6 +49,7 @@ CONFIG_SCHEMA = {
     },
     "required": [
         "alarms-db",
+        "ops-db",
         "oid_list.conf",
         "routers_community.conf",
         "ssh",
diff --git a/inventory_provider/opsdb.py b/inventory_provider/opsdb.py
new file mode 100644
index 0000000000000000000000000000000000000000..6457745ad9f6ecd26645f6d9304d0cf9ac4f04ee
--- /dev/null
+++ b/inventory_provider/opsdb.py
@@ -0,0 +1,43 @@
+import contextlib
+import logging
+
+import mysql.connector
+
+from inventory_provider.constants import DATABASE_LOGGER_NAME
+
+
+@contextlib.contextmanager
+def connection(opsdb):
+    cx = None
+    try:
+        cx = mysql.connector.connect(
+            host=opsdb["hostname"],
+            user=opsdb["username"],
+            passwd=opsdb["password"],
+            db=opsdb["dbname"])
+        yield cx
+    finally:
+        if cx:
+            cx.close()
+
+
+@contextlib.contextmanager
+def cursor(cnx):
+    csr = None
+    try:
+        csr = cnx.cursor()
+        yield csr
+    finally:
+        if csr:
+            csr.close()
+
+
+def _db_test(db, router):
+    database_logger = logging.getLogger(DATABASE_LOGGER_NAME)
+    with cursor(db) as crs:
+        query = "select model, manufacturer from equipment where name = %s"
+        crs.execute(query, (router['hostname'],))
+        for (model, manufacturer) in crs:
+            database_logger.debug("%s: %s %s" % (
+                router['hostname'], model, manufacturer))
+            yield {"model": model, "manufacturer": manufacturer}
diff --git a/inventory_provider/routes/__init__.py b/inventory_provider/routes/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/inventory_provider/routes/alarmsdb.py b/inventory_provider/routes/alarmsdb.py
new file mode 100644
index 0000000000000000000000000000000000000000..ce06752987a037d8958f227a4b32268805c4b5c9
--- /dev/null
+++ b/inventory_provider/routes/alarmsdb.py
@@ -0,0 +1,41 @@
+import functools
+import json
+
+from flask import Blueprint, request, Response, current_app
+from inventory_provider import alarmsdb
+
+routes = Blueprint("inventory-alarmsdb-query-routes", __name__)
+
+
+def require_accepts_json(f):
+    """
+    used as a route handler decorator to return an error
+    unless the request allows responses with type "application/json"
+    :param f: the function to be decorated
+    :return: the decorated function
+    """
+    @functools.wraps(f)
+    def decorated_function(*args, **kwargs):
+        # TODO: use best_match to disallow */* ...?
+        if not request.accept_mimetypes.accept_json:
+            return Response(
+                response="response will be json",
+                status=406,
+                mimetype="text/html")
+        return f(*args, **kwargs)
+    return decorated_function
+
+
+@routes.route("/test", methods=['GET', 'POST'])
+@require_accepts_json
+def alarmsdb_test():
+    config = current_app.config['INVENTORY_PROVIDER_CONFIG']
+
+    result = {}
+    with alarmsdb.connection(config['alarms-db']) as db:
+        for r in config['routers']:
+            result[r['hostname']] = list(alarmsdb._db_test(db, r))
+
+    return Response(
+        json.dumps(result),
+        mimetype="application/json")
diff --git a/inventory_provider/data_routes.py b/inventory_provider/routes/data.py
similarity index 100%
rename from inventory_provider/data_routes.py
rename to inventory_provider/routes/data.py
diff --git a/inventory_provider/job_routes.py b/inventory_provider/routes/jobs.py
similarity index 100%
rename from inventory_provider/job_routes.py
rename to inventory_provider/routes/jobs.py
diff --git a/inventory_provider/routes/opsdb.py b/inventory_provider/routes/opsdb.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b3195ecec04c04d87598918bc03d4862e07739a
--- /dev/null
+++ b/inventory_provider/routes/opsdb.py
@@ -0,0 +1,41 @@
+import functools
+import json
+
+from flask import Blueprint, request, Response, current_app
+from inventory_provider import opsdb
+
+routes = Blueprint("inventory-opsdb-query-routes", __name__)
+
+
+def require_accepts_json(f):
+    """
+    used as a route handler decorator to return an error
+    unless the request allows responses with type "application/json"
+    :param f: the function to be decorated
+    :return: the decorated function
+    """
+    @functools.wraps(f)
+    def decorated_function(*args, **kwargs):
+        # TODO: use best_match to disallow */* ...?
+        if not request.accept_mimetypes.accept_json:
+            return Response(
+                response="response will be json",
+                status=406,
+                mimetype="text/html")
+        return f(*args, **kwargs)
+    return decorated_function
+
+
+@routes.route("/test", methods=['GET', 'POST'])
+@require_accepts_json
+def opsdb_test():
+    config = current_app.config['INVENTORY_PROVIDER_CONFIG']
+
+    result = {}
+    with opsdb.connection(config['ops-db']) as db:
+        for r in config['routers']:
+            result[r['hostname']] = list(opsdb._db_test(db, r))
+
+    return Response(
+        json.dumps(result),
+        mimetype="application/json")
diff --git a/setup.py b/setup.py
index 7bd7ee5d2dc6631242a2e80c7ba373fe9655adb1..478b13ec8fadcc982eb73c70d9c350f046c691a7 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
 
 setup(
     name='inventory-provider',
-    version="0.2",
+    version="0.3",
     author='GEANT',
     author_email='swd@geant.org',
     description='Dashboard inventory provider',
diff --git a/test/test_data_routes.py b/test/test_data_routes.py
index 42878e10b79bddc64d4c509ed24f8fbfb7cf9385..03642b09a4e6dbf1fb60cb43835e7e399191072e 100644
--- a/test/test_data_routes.py
+++ b/test/test_data_routes.py
@@ -90,6 +90,12 @@ def data_config_filename(tmp_dir_name):
             "username": "xxxxxx",
             "password": "xxxxxxxx"
         },
+        "ops-db": {
+            "hostname": "xxxxxxx.yyyyy.zzz",
+            "dbname": "xxxxxx",
+            "username": "xxxxxx",
+            "password": "xxxxxxxx"
+        },
         "oid_list.conf": os.path.join(
             tmp_dir_name,
             "oid_list.conf"),
@@ -199,7 +205,7 @@ def test_routers_list(mocker, client):
         'inventory_provider.router_details.redis.StrictRedis',
         MockedRedis)
     mocker.patch(
-        'inventory_provider.data_routes.redis.StrictRedis',
+        'inventory_provider.routes.data.redis.StrictRedis',
         MockedRedis)
     rv = client.post(
         "data/routers",