diff --git a/README.md b/README.md
index 84a65db87513741d4f9e00fba9afedcdbefc103d..6479408687ac93782ed68328e8e8761772716f24 100644
--- a/README.md
+++ b/README.md
@@ -1,74 +1,13 @@
 # BRIAN Dashboard Manager
 
-## Overview
+The BRIAN Dashboard Manager is used
+provision Organizations and Dashboards in Grafana for BRIAN.
 
-This module is used to provision Organizations and Dashboards in Grafana for BRIAN.
-It implements a Flask-based webservice used only to trigger the provisioning process.
-
-The dashboards are generated from a list of interfaces obtained from Inventory Provider.
-
-Jinja templates are populated with data from these interfaces to render Dashboard JSON definitions sent to the Grafana API.
-
-Grafana API-related code lives in the `grafana` package.
-
-The `brian_dashboard_manager/grafana/provision.py` file is responsible for the entire provisioning lifecycle.
-
-Grafana API endpoints have wrapper functions in one file for relevant parts of the API, e.g. `brian_dashboard_manager/grafana/dashboard.py` for the dashboard API functions.
-
-Another example is `brian_dashboard_manager/grafana/organization.py` for the organization API functions.
-
-Some of the provisioned dashboards are not generated but are just static JSON files. These are put in the `brian_dashboard_manager/dashboards` directory. The same can be done for JSON datasource definitions in the `datasources` directory.
-
-The `brian_dashboard_manager/templating` package contains the code and Jinja templates used to render dashboard JSON. Most dashboards reuse the same templates, with the exception of NREN-specific dashboards, which has its own template.
-
-Of note is the `templating/helpers.py` file, which has all of the predicates and helper functions used to group interfaces together and generate the necessary data for the dashboard templates.
-The `templating/render.py` has functions for rendering of the various Jinja templates from the given data.
-
-## Configuration
-
-This app allows specification of a few
-example configuration parameters. These
-parameters should stored in a file formatted
-similarly to `config.json.example`, and the name
-of this file should be stored in the environment
-variable `CONFIG_FILENAME` when running the service.
-
-## Running this module
-
-This module has been tested in the following execution environments:
-
-- As an embedded Flask application.
-  For example, the application could be launched as follows:
+Documentation can be generated by running sphinx:
 
 ```bash
-$ export FLASK_APP=/path/to/brian_dashboard_manager/app.py
-$ export CONFIG_FILENAME=/path/to/config.json
-$ flask run
+sphinx-build -M html docs/source docs/build
 ```
 
-- As a `gunicorn` wsgi service.
-  - Details of `gunicorn` configuration can be found in the brian_dashboard_manager Puppet repository.
-
-## Protocol Specification
-
-The following resources can be requested from the webservice.
-
-### resources
-Any non-empty responses are JSON formatted messages.
-
-## /update
-
-This resource is used to trigger the provisioning to Grafana.
-It responds to the request immediately after starting the provisioning process.
-
-```json
-{
-  "$schema": "http://json-schema.org/draft-07/schema#",
-  "type": "object",
-  "properties": {
-    "message": {
-      "type": "string"
-    }
-  }
-}
-```
+The documents should be viewable in the
+workspace of the most recent [Jenkins job](https://jenkins.geant.org/job/brian-dashboard-manager/ws/docs/build/html/index.html).
diff --git a/brian_dashboard_manager/grafana/__init__.py b/brian_dashboard_manager/grafana/__init__.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..46c928562e86e5c599ef512fd2e529cf74206914 100644
--- a/brian_dashboard_manager/grafana/__init__.py
+++ b/brian_dashboard_manager/grafana/__init__.py
@@ -0,0 +1,22 @@
+"""
+Grafana module
+===============
+
+Grafana API-related code.
+
+Provisioning
+---------------
+.. automodule:: brian_dashboard_manager.grafana.provision
+
+
+Grafana API
+-------------
+.. automodule:: brian_dashboard_manager.grafana.dashboard
+
+
+Organizations
+----------------
+.. automodule:: brian_dashboard_manager.grafana.organization
+
+
+"""
diff --git a/brian_dashboard_manager/grafana/dashboard.py b/brian_dashboard_manager/grafana/dashboard.py
index 19c1fee56a16c75aa97aca48bb6bdbec43fc50c7..7763af5941be0281dab11ec00955a7d09fdaa906 100644
--- a/brian_dashboard_manager/grafana/dashboard.py
+++ b/brian_dashboard_manager/grafana/dashboard.py
@@ -1,3 +1,6 @@
+"""
+Grafana Dashhboard API endpoints wrapper functions.
+"""
 import logging
 import os
 import json
diff --git a/brian_dashboard_manager/grafana/organization.py b/brian_dashboard_manager/grafana/organization.py
index 4c01096f279801cbbbe7730cd0281a28c112c746..d075660ea874aec41a559abe3b40786dad8d948d 100644
--- a/brian_dashboard_manager/grafana/organization.py
+++ b/brian_dashboard_manager/grafana/organization.py
@@ -1,3 +1,7 @@
+"""
+Grafana Organization management helpers.
+
+"""
 import random
 import string
 import logging
diff --git a/brian_dashboard_manager/grafana/provision.py b/brian_dashboard_manager/grafana/provision.py
index 90eae8885608bf52e9ad553356d0252ec612ce0e..bdb0e29bd093a7f527160fcbe8ab35e709d33e8b 100644
--- a/brian_dashboard_manager/grafana/provision.py
+++ b/brian_dashboard_manager/grafana/provision.py
@@ -1,3 +1,7 @@
+"""
+This module is responsible for the
+entire provisioning lifecycle.
+"""
 import logging
 import time
 from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
diff --git a/brian_dashboard_manager/routes/update.py b/brian_dashboard_manager/routes/update.py
index 681c8679fd366fef2ecd856a695aca24d39a207a..ed538e41ebe6f161dea5235e464537b0a8ac3908 100644
--- a/brian_dashboard_manager/routes/update.py
+++ b/brian_dashboard_manager/routes/update.py
@@ -6,6 +6,16 @@ from brian_dashboard_manager import CONFIG_KEY
 
 routes = Blueprint("update", __name__)
 
+UPDATE_RESPONSE_SCHEMA = {
+    '$schema': 'http://json-schema.org/draft-07/schema#',
+    'type': 'object',
+    'properties': {
+        'message': {
+            'type': 'string'
+        }
+    }
+}
+
 
 @routes.after_request
 def after_request(resp):
@@ -14,6 +24,19 @@ def after_request(resp):
 
 @routes.route('/', methods=['GET'])
 def update():
+    """
+    This resource is used to trigger the provisioning to Grafana.
+
+    It responds to the request immediately after starting
+    the provisioning process.
+
+    The response will be formatted according to the following schema:
+
+    .. asjson::
+       brian_dashboard_manager.routes.update.UPDATE_RESPONSE_SCHEMA
+
+    :return: json
+    """
     executor = ThreadPoolExecutor(max_workers=1)
     executor.submit(provision, current_app.config[CONFIG_KEY])
     return {'data': {'message': 'Provisioning dashboards!'}}
diff --git a/brian_dashboard_manager/templating/__init__.py b/brian_dashboard_manager/templating/__init__.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..76814c2f1654252d2e9d885b5b3574d91d48259c 100644
--- a/brian_dashboard_manager/templating/__init__.py
+++ b/brian_dashboard_manager/templating/__init__.py
@@ -0,0 +1,28 @@
+"""
+Code and
+Jinja templates used to render dashboard JSON.
+Most dashboards reuse the
+same templates, with the exception of
+NREN-specific dashboards, which has
+its own template.
+
+
+Templates
+-----------
+Some of the provisioned dashboards are not generated but are just static
+JSON files. These are put in the
+`brian_dashboard_manager/dashboards` directory.
+The same can be done for JSON datasource definitions in
+the `datasources` directory.
+
+
+Helpers
+---------
+.. automodule:: brian_dashboard_manager.templating.helpers
+
+
+Rendering
+---------
+.. automodule:: brian_dashboard_manager.templating.render
+
+"""
diff --git a/brian_dashboard_manager/templating/helpers.py b/brian_dashboard_manager/templating/helpers.py
index 1171c9fc88c0f39837382a46aeb6c7e575698555..fea1a1f194874aefc3968fb662c43020351666fd 100644
--- a/brian_dashboard_manager/templating/helpers.py
+++ b/brian_dashboard_manager/templating/helpers.py
@@ -1,3 +1,8 @@
+"""
+Predicates
+and helper functions used to group interfaces together and generate the
+necessary data for the dashboard templates.
+"""
 import re
 import logging
 import json
diff --git a/brian_dashboard_manager/templating/render.py b/brian_dashboard_manager/templating/render.py
index dede30c87a9d9869551a1680e183e73a2060d1e4..2aa06bf9258275ffc8dd239e0003588bcd7e1a40 100644
--- a/brian_dashboard_manager/templating/render.py
+++ b/brian_dashboard_manager/templating/render.py
@@ -1,3 +1,7 @@
+"""
+Methods for rendering of the
+various Jinja templates from the given data.
+"""
 import os
 import json
 import jinja2
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..d0c3cbf1020d5c292abdedf27627c6abe25e2293
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS    ?=
+SPHINXBUILD   ?= sphinx-build
+SOURCEDIR     = source
+BUILDDIR      = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/source/conf.py b/docs/source/conf.py
new file mode 100644
index 0000000000000000000000000000000000000000..a3a3646fdec3cfa425d49b626b7c0cebb4edfc7b
--- /dev/null
+++ b/docs/source/conf.py
@@ -0,0 +1,101 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+
+from importlib import import_module
+from docutils.parsers.rst import Directive
+from docutils import nodes
+from sphinx import addnodes
+import json
+import os
+import sys
+
+sys.path.insert(0, os.path.abspath(
+    os.path.join(
+        os.path.dirname(__file__),
+        '..', '..', 'inventory_provider')))
+
+
+class RenderAsJSON(Directive):
+    # cf. https://stackoverflow.com/a/59883833
+
+    required_arguments = 1
+
+    def run(self):
+        module_path, member_name = self.arguments[0].rsplit('.', 1)
+
+        member_data = getattr(import_module(module_path), member_name)
+        code = json.dumps(member_data, indent=2)
+
+        literal = nodes.literal_block(code, code)
+        literal['language'] = 'json'
+
+        return [
+                addnodes.desc_name(text=member_name),
+                addnodes.desc_content('', literal)
+        ]
+
+
+def setup(app):
+    app.add_directive('asjson', RenderAsJSON)
+
+
+# -- Project information -----------------------------------------------------
+
+project = 'BRIAN Dashboard Manager'
+copyright = '2021, swd@geant.org'
+author = 'swd@geant.org'
+
+# The full version, including alpha/beta/rc tags
+release = '0.0'
+
+
+# -- General configuration ---------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+  'sphinx_rtd_theme',
+  'sphinx.ext.autodoc',
+  'sphinx.ext.coverage'
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = []
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'sphinx_rtd_theme'
+# html_theme = 'alabaster'
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Both the class’ and the __init__ method’s docstring
+# are concatenated and inserted.
+autoclass_content = "both"
+autodoc_typehints = "none"
diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst
new file mode 100644
index 0000000000000000000000000000000000000000..5f35ebc14879888fe5692152ae72176b34f0f25a
--- /dev/null
+++ b/docs/source/configuration.rst
@@ -0,0 +1,32 @@
+
+Configuration and Running
+=========================
+
+Configuration
+-------------
+
+This app allows specification of a few
+example configuration parameters. These
+parameters should stored in a file formatted
+similarly to `config.json.example`, and the name
+of this file should be stored in the environment
+variable `CONFIG_FILENAME` when running the service.
+
+Running this module
+---------------------
+
+This module has been tested in the following execution environments:
+
+* As an embedded Flask application.
+  For example, the application could be launched as follows:
+
+.. code-block:: python
+
+   export FLASK_APP=/path/to/brian_dashboard_manager/app.py
+   export CONFIG_FILENAME=/path/to/config.json
+   flask run
+
+* As a `gunicorn` wsgi service.
+
+  * Details of `gunicorn` configuration can be found in the
+    brian_dashboard_manager Puppet repository.
\ No newline at end of file
diff --git a/docs/source/index.rst b/docs/source/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..97444a54aef7962b68d0cc7ec16d4ccf7d46fad0
--- /dev/null
+++ b/docs/source/index.rst
@@ -0,0 +1,20 @@
+.. BRIAN Dashboard Manager documentation master file, created by
+   sphinx-quickstart on Tue Mar 16 14:42:57 2021.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+
+Inventory Provider
+==================
+
+The BRIAN Dashboard Manager is used
+provision Organizations and Dashboards in Grafana for BRIAN.
+
+
+.. toctree::
+   :maxdepth: 3
+   :caption: Contents:
+
+   configuration
+   overview
+   protocol
diff --git a/docs/source/overview.rst b/docs/source/overview.rst
new file mode 100644
index 0000000000000000000000000000000000000000..063ba7ee1154fa4fcdb8dd5a5af1140b9961b305
--- /dev/null
+++ b/docs/source/overview.rst
@@ -0,0 +1,14 @@
+
+Overview
+=========================
+
+This module is used to provision Organizations and Dashboards inGrafana for BRIAN.
+
+The dashboards are generated from a list of interfaces obtained from Inventory Provider.
+
+Jinja templates are populated with data from these interfaces to render
+Dashboard JSON definitions sent to the Grafana API.
+
+.. automodule:: brian_dashboard_manager.grafana
+
+.. automodule:: brian_dashboard_manager.templating
diff --git a/docs/source/protocol.rst b/docs/source/protocol.rst
new file mode 100644
index 0000000000000000000000000000000000000000..bb1ebfdf9b9b4cce9e5c1b8fcc0875325e192086
--- /dev/null
+++ b/docs/source/protocol.rst
@@ -0,0 +1,19 @@
+
+Protocol
+=========================
+
+
+This module implements a Flask-based webservice used only to
+trigger the provisioning process.
+
+The following resources can be requested from the webservice.
+
+resources
+-----------
+Any non-empty responses are JSON formatted messages.
+
+
+/update
+*********
+
+.. autofunction::  brian_dashboard_manager.routes.update.update
diff --git a/requirements.txt b/requirements.txt
index f90bc88a6934a897286fbd219cf8157f55ad5f6e..c4cf279340074c24ff4fad9e7766c9f3441f0138 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,11 @@
 requests
 jsonschema
 flask
+jinja2
+
 pytest
 pytest-mock
 responses
-jinja2
\ No newline at end of file
+sphinx
+sphinx-rtd-theme
+
diff --git a/tox.ini b/tox.ini
index 31480e823de253929907310ceab787c6e312ef51..47b349ff0fce3c58ee87abb1c0eafc8ee8330afa 100644
--- a/tox.ini
+++ b/tox.ini
@@ -16,4 +16,5 @@ commands =
     coverage xml
     coverage html
     coverage report --fail-under 75
-    flake8
\ No newline at end of file
+    flake8
+    sphinx-build -M html docs/source docs/build