diff --git a/brian_polling_manager/__init__.py b/brian_polling_manager/__init__.py
index 50c8ab59ae75d2781aea5852c1fd6dd8fe1667f9..d3dbc19f3741c5d05983ac8f9878a570c22d5247 100644
--- a/brian_polling_manager/__init__.py
+++ b/brian_polling_manager/__init__.py
@@ -1,37 +1,37 @@
-# """
-# automatically invoked app factory
-# """
-# import logging
-# import os
-#
-# from flask import Flask
-#
-# import dashboard_v3_webapp.config as config
-#
-# CONFIG_KEY = 'CONFIG_PARAMS'
-# SESSION_SECRET = 'super-secret'
-#
-#
-# def create_app():
-#     """
-#     overrides default settings with those found
-#     in the file read from env var CONFIG_FILENAME
-#
-#     :return: a new flask app instance
-#     """
-#
-#     app_config = config.DEFAULT_PARAMS
-#     if 'CONFIG_FILENAME' in os.environ:
-#         with open(os.environ['CONFIG_FILENAME']) as f:
-#             app_config.update(config.load(f))
-#
-#     app = Flask(__name__)
-#     app.secret_key = SESSION_SECRET
-#     app.config[CONFIG_KEY] = app_config
-#
-#     from dashboard_v3_webapp import api
-#     app.register_blueprint(api.routes, url_prefix='/api')
-#
-#     logging.info('Flask app initialized')
-#     # environment.setup_logging()
-#     return app
+"""
+automatically invoked app factory
+"""
+import logging
+import os
+
+from flask import Flask
+
+from brian_polling_manager import configuration
+
+CONFIG_KEY = 'CONFIG_PARAMS'
+SESSION_SECRET = 'super-secret'
+
+
+def create_app():
+    """
+    overrides default settings with those found
+    in the file read from env var CONFIG_FILENAME
+
+    :return: a new flask app instance
+    """
+
+    app = Flask(__name__)
+    app.secret_key = SESSION_SECRET
+
+    if 'CONFIG_FILENAME' in os.environ:
+        with open(os.environ['CONFIG_FILENAME']) as f:
+            app.config[CONFIG_KEY] = configuration.load_config(f)
+    else:
+        # this inits with the defaults
+        app.config[CONFIG_KEY] = configuration.load_config()
+
+    from brian_polling_manager import api
+    app.register_blueprint(api.routes, url_prefix='/api')
+
+    logging.info('Flask app initialized')
+    return app
diff --git a/brian_polling_manager/api.py b/brian_polling_manager/api.py
new file mode 100644
index 0000000000000000000000000000000000000000..9e7fbdade88ddf6e87127330aabace52bd55f8d2
--- /dev/null
+++ b/brian_polling_manager/api.py
@@ -0,0 +1,163 @@
+"""
+These endpoints are used to update BRIAN snmp polling checks to Sensu
+
+.. contents:: :local:
+
+
+/api/version
+---------------------
+
+.. autofunction:: brian_polling_manager.api.version
+
+
+/api/update
+-----------------------------
+
+.. autofunction:: brian_polling_manager.api.update
+
+"""
+import functools
+import logging
+import pkg_resources
+
+from flask import Blueprint, current_app, request, Response, jsonify
+
+from brian_polling_manager import CONFIG_KEY
+from brian_polling_manager.main import refresh
+
+routes = Blueprint("api-routes", __name__)
+logger = logging.getLogger(__name__)
+
+
+API_VERSION = '0.1'
+
+VERSION_SCHEMA = {
+    '$schema': 'http://json-schema.org/draft-07/schema#',
+
+    'definitions': {
+        'latch': {
+            'type': 'object',
+            'properties': {
+                'current': {'type': 'integer'},
+                'next': {'type': 'integer'},
+                'this': {'type': 'integer'},
+                'failure': {'type': 'boolean'},
+                'pending': {'type': 'boolean'},
+                'timestamp': {'type': 'number'}
+            },
+            'required': ['current', 'next', 'this', 'pending', 'failure'],
+            'additionalProperties': False
+        }
+    },
+
+    'type': 'object',
+    'properties': {
+        'api': {
+            'type': 'string',
+            'pattern': r'\d+\.\d+'
+        },
+        'module': {
+            'type': 'string',
+            'pattern': r'\d+\.\d+'
+        },
+        'latch': {'$ref': '#/definitions/latch'}
+    },
+    'required': ['api', 'module'],
+    'additionalProperties': False
+}
+
+
+class APIUpstreamError(Exception):
+    def __init__(self, message, status_code=504):
+        super().__init__()
+        self.message = message
+        self.status_code = status_code
+
+
+@routes.errorhandler(APIUpstreamError)
+def handle_request_error(error):
+    logger.error(f'api error: {error.message}')
+    return Response(
+        response=error.message,
+        status=error.status_code,
+        mimetype='text/html')
+
+
+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.after_request
+def after_request(response):
+    """
+    Generic function to do additional logging of requests & responses.
+
+    :param response:
+    :return:
+    """
+    if response.status_code != 200:
+
+        try:
+            data = response.data.decode('utf-8')
+        except Exception:
+            # never expected to happen, but we don't want any failures here
+            logging.exception('INTERNAL DECODING ERROR')
+            data = 'decoding error (see logs)'
+
+        logger.warning(
+            f'[{response.status_code}] {request.method} {request.path} {data}')
+    else:
+        logger.info(
+            f'[{response.status_code}] {request.method} {request.path}')
+    return response
+
+
+@routes.route('/version', methods=['GET', 'POST'])
+@require_accepts_json
+def version():
+    """
+    Returns a json object with information about the module version.
+
+    The response will be formatted according to the following schema:
+
+    .. asjson:: brian_polling_manager.api.VERSION_SCHEMA
+
+    :return:
+    """
+    version_params = {
+        'api': API_VERSION,
+        'module':
+            pkg_resources.get_distribution('brian_polling_manager').version
+    }
+    return jsonify(version_params)
+
+
+@routes.route('/update', methods=['GET', 'POST'])
+@require_accepts_json
+def update():
+    """
+    Update checks based on current inventory provider state.
+
+    The response will be formatted according to the following schema:
+
+    .. asjson:: brian_polling_manager.main.REFRESH_RESULT_SCHEMA
+
+    :return:
+    """
+    response = refresh(config=current_app.config[CONFIG_KEY])
+    return jsonify(response)
diff --git a/brian_polling_manager/configuration.py b/brian_polling_manager/configuration.py
index c09ab5d03896579aab2b273bd5cb65b36858a90c..bbb136dd4601b54c3863813d80a39eb4b2463f6d 100644
--- a/brian_polling_manager/configuration.py
+++ b/brian_polling_manager/configuration.py
@@ -204,7 +204,7 @@ def _setup_logging(filename=None):
         # logging.config.dictConfig(json.loads(f.read()))
 
 
-def load_config(file):
+def load_config(file=None):
     """
     loads, validates and returns configuration parameters
 
@@ -213,7 +213,7 @@ def load_config(file):
     :raises: json.JSONDecodeError, jsonschema.ValidationError,
         OSError, AttributeError, ValueError, TypeError, ImportError
     """
-    if file is None:
+    if not file:
         config = _DEFAULT_CONFIG
     else:
         config = json.loads(file.read())
diff --git a/brian_polling_manager/interfaces.py b/brian_polling_manager/interfaces.py
index 30de108cd3c1a4f47efcda946b88e9c6a9be9602..2c0adef6f595f21ee42d64d32c1a89394cf40727 100644
--- a/brian_polling_manager/interfaces.py
+++ b/brian_polling_manager/interfaces.py
@@ -97,6 +97,7 @@ def refresh(sensu_params, state):
     for name in extra_checks:
         sensu.delete_check(sensu_params, name)
 
+    # cf. main.REFRESH_RESULT_SCHEMA
     return {
         'checks': len(ifc_checks),
         'input': interfaces,
diff --git a/brian_polling_manager/main.py b/brian_polling_manager/main.py
index 03969f48f04caaa424ac6fd4a9ded7bff9f9974d..947a37063f4649101ac8a6ac5001314b93172753 100644
--- a/brian_polling_manager/main.py
+++ b/brian_polling_manager/main.py
@@ -33,14 +33,43 @@ from brian_polling_manager import inventory, interfaces, configuration
 
 logger = logging.getLogger(__name__)
 
+REFRESH_RESULT_SCHEMA = {
+    '$schema': 'http://json-schema.org/draft-07/schema#',
+    'definitions': {
+        'refresh-result': {
+            'type': 'object',
+            'properties': {
+                'checks': {'type': 'integer'},
+                'input': {'type': 'integer'},
+                'created': {'type': 'integer'},
+                'updated': {'type': 'integer'},
+                'deleted': {'type': 'integer'}
+            },
+            'required': ['checks', 'input', 'created', 'updated', 'deleted'],
+            'additionalProperties': False
+        }
+    },
+    'type': 'object',
+    'properties': {
+        'interfaces': {'$ref': '#/definitions/refresh-result'}
+    },
+    'required': ['interfaces'],
+    'additionalProperties': False
+}
+
 
 def refresh(config, force=False):
     """
     reload inventory data & update sensu checks
 
+    The output will be a dict formatted according to the following schema:
+
+    .. asjson::
+        brian_polling_manager.main.REFRESH_RESULT_SCHEMA
+
     :param config: a dict returned by configuration.load_config
     :param force: if True, reload inventory data even if timestamp is same
-    :return:
+    :return: a dict, formatted as above
     """
     state = configuration.State(config['statedir'])
     last = inventory.last_update_timestamp(config['inventory'])
@@ -48,7 +77,9 @@ def refresh(config, force=False):
         state.last = last
         state.interfaces = inventory.load_interfaces(config['inventory'])
 
-    result = interfaces.refresh(config['sensu'], state)
+    result = {
+        'interfaces': interfaces.refresh(config['sensu'], state)
+    }
 
     statsd_config = config.get('statsd', None)
     if statsd_config:
@@ -56,13 +87,14 @@ def refresh(config, force=False):
             host=statsd_config['hostname'],
             port=statsd_config['port'],
             prefix=f'{statsd_config["prefix"]}_interfaces')
-        statsd.gauge('checks', result['checks'])
-        statsd.gauge('input', result['input'])
-        statsd.gauge('created', result['created'])
-        statsd.gauge('updated', result['updated'])
-        statsd.gauge('deleted', result['deleted'])
+        statsd.gauge('checks', result['interfaces']['checks'])
+        statsd.gauge('input', result['interfaces']['input'])
+        statsd.gauge('created', result['interfaces']['created'])
+        statsd.gauge('updated', result['interfaces']['updated'])
+        statsd.gauge('deleted', result['interfaces']['deleted'])
 
-    return statsd_config
+    jsonschema.validate(result, REFRESH_RESULT_SCHEMA)  # sanity
+    return result
 
 def _validate_config(_ctx, _param, file):
     """
@@ -95,7 +127,7 @@ def cli(config, force):
     """
     Update BRIAN snmp checks based on Inventory Provider data.
     """
-    refresh(config, force)
+    logger.info(json.dumps(refresh(config, force)))
 
 
 if __name__ == '__main__':
diff --git a/docs/source/api.rst b/docs/source/api.rst
new file mode 100644
index 0000000000000000000000000000000000000000..efea063542a57dd21192254370ab61c5dc45f421
--- /dev/null
+++ b/docs/source/api.rst
@@ -0,0 +1,6 @@
+
+HTTP API
+===================================================
+
+.. automodule:: brian_polling_manager.api
+
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 4ced46d553c7bfc2978b28ca97fa62a94d276141..006d51f03004ee54b7dd4a21996274b94e633c8a 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -17,3 +17,4 @@ Sensu checks for polling the data required by BRIAN.
    :caption: Contents:
 
    main
+   api
diff --git a/requirements.txt b/requirements.txt
index 14c092051717d2a1d81ef626c0e33e155083fb67..a5a0af502b4e289530624c546268288480910240 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,6 +2,7 @@ click
 jsonschema
 requests
 statsd
+flask
 
 pytest
 responses
diff --git a/setup.py b/setup.py
index cf6c751e088fecda7fd1a4f1df24acc0c80a578a..59ddd2c76c53cfbd00d4366fd7c9ac62f482ac9d 100644
--- a/setup.py
+++ b/setup.py
@@ -12,11 +12,12 @@ setup(
         'click',
         'requests',
         'jsonschema',
-        'statsd'
+        'statsd',
+        'flask'
     ],
     entry_points={
         'console_scripts': [
-            'brian-polling-manager=brian_polling_manager.cli:main'
+            'brian-polling-manager=brian_polling_manager.main:cli'
         ]
     },
     include_package_data=True,