diff --git a/README.md b/README.md
index b993b2c7d2b04303fde0b357b63686f8868001c7..7067dcb4a7686030b9ed926ed06df5f1c21ee0a6 100644
--- a/README.md
+++ b/README.md
@@ -1,1688 +1,12 @@
-
-* [Inventory Provider](#inventory-provider)
-  * [Overview](#overview)
-  * [Configuration](#configuration)
-  * [Running this module](#running-this-module)
-  * [Protocol Specification](#protocol-specification)
-    * [/data/version](#dataversion)
-    * [/data/routers](#datarouters)
-    * [/data/interfaces](#datainterfaces)
-    * [/data/pop](#datapop)
-    * [/jobs/update](#jobsupdate)
-    * [/jobs/log](#jobslog)
-    * [/jobs/reload-router-config](#jobsreload-router-config)
-    * [/jobs/check-task-status](#jobscheck-task-status)
-    * [/classifier/juniper-link-info](#classifierjuniper-link-info)
-    * [/classifier/peer-info](#classifierpeer-info)
-    * [/classifier/infinera-lambda-info](#classifierinfinera-lambda-info)
-    * [/classifier/coriant-info](#classifiercoriant-info)
-    * [/poller/interfaces](#pollerinterfaces)
-    * [/lg/routers](#lgrouters)
-    * [/msr/access-services](#msraccess-services)
-    * [/testing/flushdb](#testingflushdb)
-    * [/testing/infinera-dna-addresses](#testinginfinera-dna-addresses)
-    * [/testing/coriant-tnms-addresses](#testingcoriant-tnms-addresses)
-    * [/testing/juniper-server-addresses](#testingjuniper-server-addresses)
-    * [/data/snmp](#datasnmp)
-    * [/data/bgp](#databgp)
-  * [Backend (Redis) Storage Schema](#backend-redis-storage-schema)
-
-
-
 # Inventory Provider
 
+The Inventory Provider
+acts as a single point of truth for
+information about the GÉANT network, exposed by
+by an HTTP API.
 
-## Overview
-
-This module implements a Flask-based webservice which
-provides some access to GÉANT network router inventory
-information.
-
-The webservice is communicates with clients over HTTP.
-Responses to valid requests are returned as JSON messages.
-The server will therefore return an error unless
-`application/json` is in the `Accept` request header field.
-
-HTTP communication and JSON grammar details are
-beyond the scope of this document.
-Please refer to [RFC 2616](https://tools.ietf.org/html/rfc2616)
-and www.json.org for more details.
-
-
-## Configuration
-
-Several configuration files must be provided when launching
-the web service.
-
-The runtime-accessible filename of the first configuration file
-must be stored in the environment variable
-`SETTINGS_FILENAME`.
-
-The configuration file is python and defines a single variable.
-The following is an example:
-
-```python
-INVENTORY_PROVIDER_CONFIG_FILENAME = "/somepath/config.json"
-ENABLE_TESTING_ROUTES = True
-```
-
-- `INVENTORY_PROVIDER_CONFIG_FILENAME`: [REQUIRED] Run-time accessible filename
-of a json file containing the server configuration parameters.  This file
-must be formatted according to the following json schema:
-
-    ```json
-    {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-
-        "definitions": {
-            "timeout": {
-                "type": "number",
-                "maximum": 10,  # sanity
-                "exclusiveMinimum": 0
-            },
-            "database-credentials": {
-                "type": "object",
-                "properties": {
-                    "hostname": {"type": "string"},
-                    "dbname": {"type": "string"},
-                    "username": {"type": "string"},
-                    "password": {"type": "string"}
-                },
-                "required": ["hostname", "dbname", "username", "password"],
-                "additionalProperties": False
-            },
-            "ssh-credentials": {
-                "type": "object",
-                "properties": {
-                    "username": {"type": "string"},
-                    "private-key": {"type": "string"},
-                    "known-hosts": {"type": "string"}
-                },
-                "required": ["private-key", "known-hosts"],
-                "additionalProperties": False
-            },
-            "redis-credentials": {
-                "type": "object",
-                "properties": {
-                    "hostname": {"type": "string"},
-                    "port": {"type": "integer"},
-                    "socket_timeout": {"$ref": "#/definitions/timeout"}
-                },
-                "required": ["hostname", "port"],
-                "additionalProperties": False
-            },
-            "redis-sentinel-config": {
-                "type": "object",
-                "properties": {
-                    "hostname": {"type": "string"},
-                    "port": {"type": "integer"},
-                    "name": {"type": "string"},
-                    "redis_socket_timeout": {"$ref": "#/definitions/timeout"},
-                    "sentinel_socket_timeout": {"$ref": "#/definitions/timeout"}
-                },
-                "required": ["hostname", "port", "name"],
-                "additionalProperties": False
-            },
-            "interface-address": {
-                "type": "object",
-                "properties": {
-                    "address": {"type": "string"},
-                    "network": {"type": "string"},
-                    "interface": {"type": "string"},
-                    "router": {"type": "string"}
-                },
-                "required": ["address", "network", "interface", "router"],
-                "additionalProperties": False
-            }
-        },
-
-        "type": "object",
-        "properties": {
-            "ops-db": {"$ref": "#/definitions/database-credentials"},
-            "ssh": {"$ref": "#/definitions/ssh-credentials"},
-            "redis": {"$ref": "#/definitions/redis-credentials"},
-            "sentinel": {"$ref": "#/definitions/redis-sentinel-config"},
-            "redis-databases": {
-                "type": "array",
-                "minItems": 1,
-                "items": {"type": "integer"}
-            },
-            "managed-routers": {"type": "string"},
-            "unmanaged-interfaces": {
-                "type": "array",
-                "items": {"$ref": "#/definitions/interface-address"}
-            }
-        },
-        "oneOf": [
-            {
-                "required": [
-                    "ops-db",
-                    "ssh",
-                    "redis",
-                    "redis-databases",
-                    "managed-routers"]
-            },
-            {
-                "required": [
-                    "ops-db",
-                    "ssh",
-                    "sentinel",
-                    "redis-databases",
-                    "managed-routers"]
-            }
-        ],
-        "additionalProperties": False
-    }
-    ```
-
-
-- `ENABLE_TESTING_ROUTES`: [OPTIONAL (default value: False)]
-Flat (can be any value that evaluates to True) to enable
-routes to special utilities used for testing.
-*This must never be enabled in a production environment.*
-
-
-## 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=app.py
-$ export SETTINGS_FILENAME=settings.cfg
-$ flask run
-```
-
-- As an Apache/`mod_wsgi` service.
-  - Details of Apache and `mod_wsgi`
-    configuration are beyond the scope of this document.
-
-- As a `gunicorn` wsgi service.
-  - Details of `gunicorn` configuration are
-    beyond the scope of this document.
-
-
-## Protocol Specification
-
-The following resources can be requested from the webservice.
-
-### resources
-
-Any non-empty responses are JSON formatted messages.
-
-#### /data/version
-
-  * /data/version
-
-  The response will be an object
-  containing the module and protocol versions of the
-  running server and will be formatted as follows:
-
-  ```json
-  {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-        "type": "object",
-        "properties": {
-            "api": {
-                "type": "string",
-                "pattern": r'\d+\.\d+'
-            },
-            "module": {
-                "type": "string",
-                "pattern": r'\d+\.\d+'
-            }
-        },
-        "required": ["api", "module"],
-        "additionalProperties": False
-    }
-  ```
-
-#### /data/routers
-
-  * /data/routers
-
-  The response will be a list of router hostnames
-  for which information is available and will be
-  formatted as follows:
-
-  ```json
-  {
-      "$schema": "http://json-schema.org/draft-07/schema#",
-      "type": "array",
-      "items": {"type": "string"}
-  }
-  ```
-
-#### /data/interfaces
-
-  * /data/interfaces</*`hostname`*>
-
-  The response will be a list of information about
-   the interfaces present on the requested host
-   and will be formatted as follows:
-
-    ```json
-    {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-        "type": "array",
-        "items": {
-            "type": "object",
-            "properties": {
-                "name": {"type": "string"},
-                "router": {"type": "string"},
-                "description": {"type": "string"},
-                "ipv4": {
-                    "type": "array",
-                    "items": {"type": "string"}
-                },
-                "ipv6": {
-                    "type": "array",
-                    "items": {"type": "string"}
-                }
-            },
-            "required": ["name", "description", "router", "ipv4", "ipv6"],
-            "additionalProperties": False
-        }
-    }
-    ```
-
-#### /data/pop
-
-  * /data/pop/*`name`*
-
-  Returns location information for the equipment identified
-  by `name`.
-
-  The response will be formatted as follows:
-
-    ```json
-    {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-        "type": "object",
-
-        "definitions": {
-            "pop-info": {
-                "type": "object",
-                "properties": {
-                    "name": {"type": "string"},
-                    "abbreviation": {"type": "string"},
-                    "country": {"type": "string"},
-                    "city": {"type": "string"},
-                    "longitude": {"type": "number"},
-                    "latitude": {"type": "number"}
-                },
-                "required": [
-                    "name",
-                    "abbreviation",
-                    "country",
-                    "city",
-                    "longitude",
-                    "latitude"
-                ],
-                "additionalProperties": False
-            },
-            "equipment-info": {
-                "type": "object",
-                "properties": {
-                    'equipment-name': {"type": "string"},
-                    'status': {"type": "string"},
-                    'pop': {"$ref": "#/definitions/pop-info"}
-                },
-                "required": [
-                    "equipment-name",
-                    "status",
-                    "pop"
-                ],
-                "additionalProperties": False
-
-            }
-        },
-
-        "type": "array",
-        "items": {"$ref": "#/definitions/equipment-info"}
-    }
-    ```
-
-#### /jobs/update
-
-  * /jobs/update
-
-  This resource updates the inventory network data for juniper devices.
-  The function completes asynchronously and a list of outstanding
-  task id's is returned so the caller can
-  use `/jobs/check-task-status` to determine when all jobs
-  are finished.  The response will be formatted as follows:
-
-  ```json
-  {
-    "$schema": "http://json-schema.org/draft-07/schema#",
-    "type": "array",
-    "items": {"type": "string"}
-  }
-  ```
-
-#### /jobs/log
-
-  * /jobs/log
-
-  This resource returns the state of the previous (or current)
-  tasks associated with a call to `/jobs/update`.  The response
-  contains error or warning messages, if any were generated.
-
-  ```json
-  {
-    "$schema": "http://json-schema.org/draft-07/schema#",
-    "type": "object",
-    "properties": {
-      "pending": {"type": "array", "items": {"type": "string"}},
-      "errors": {"type": "array", "items": {"type": "string"}},
-      "failed": {"type": "array", "items": {"type": "string"}},
-      "warnings": {"type": "array", "items": {"type": "string"}},
-    },
-    "required": ["pending", "errors", "failed", "warnings"],
-    "additionalProperties": False
-  }
-  ```
-
-#### /jobs/reload-router-config
-
-  * /jobs/reload-router-config/*`equipment-name`*
-
-  This resource updates the inventory network data for
-  the identified juniper device.  This function completes
-  asynchronously and returns the same value as
-  `/jobs/update`, except the return contains exactly
-  one task id.
-
-#### /jobs/check-task-status
-
-  * /jobs/check-task-status/*`task-id`*
-
-  This resource returns the current status of
-   an asynchronous task started by `/jobs/update`
-   or `jobs/reload-router-config`.  The return value
-   will be formatted as follows:
-
-  ```json
-  {
-    "$schema": "http://json-schema.org/draft-07/schema#",
-    "type": "object",
-    "properties": {
-        "id": {"type": "string"},
-        "status": {"type": "string"},
-        "exception": {"type": "boolean"},
-        "ready": {"type": "boolean"},
-        "success": {"type": "boolean"},
-        "result": {"type": "object"}
-    },
-    "required": ["id", "status", "exception", "ready", "success"],
-    "additionalProperties": False
-  }
-  ```
-
-#### /classifier/juniper-link-info
-
-  * /classifier/juniper-link-info/*`source-equipment`*/*`source-interface`*
-
-  The source-equipment is the equipment that causes the trap, not the NMS that
-  sends it.
-
-  The response will be an object containing the
-  metadata, formatted according to the following schema:
-
-    ```json
-    {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-        "type": "object",
-
-        "definitions": {
-            "location-endpoint": {
-                "type": "object",
-                "properties": {
-                    "equipment": {"type": "string"},
-                    "name": {"type": "string"},
-                    "abbreviation": {"type": "string"}
-                },
-                "required": ["equipment", "name", "abbreviation"],
-                "additionalProperties": False
-            },
-            "location": {
-                "type": "object",
-                "properties": {
-                    "a": {"$ref": "#/definitions/location-endpoint"},
-                    "b": {"$ref": "#/definitions/location-endpoint"}
-                },
-                "required": ["a"],
-                "additionalProperties": False
-            },
-            "locations-list": {
-                "type": "array",
-                "items": {"$ref": "#/definitions/location"}
-            },
-            "ip-address": {
-                "type": "string",
-                "oneOf": [
-                    {"pattern": r'^(\d+\.){3}\d+$'},
-                    {"pattern": r'^([a-f\d]{4}:){7}[a-f\d]{4}$'}
-                ]
-            },
-            "ipv4-interface-address": {
-                "type": "string",
-                "pattern": r'^(\d+\.){3}\d+/\d+$'
-            },
-            "ipv6-interface-address": {
-                "type": "string",
-                "pattern": r'^[a-f\d:]+/\d+$'
-            },
-            "snmp-info": {
-                "type": "object",
-                "properties": {
-                    "community": {"type": "string"},
-                    "index": {"type": "integer"}
-                },
-                "required": ["community", "index"],
-                "additionalProperties": False
-            },
-            "interface-info": {
-                "type": "object",
-                "properties": {
-                    "name": {"type": "string"},
-                    "description": {"type": "string"},
-                    "ipv4": {
-                        "type": "array",
-                        "items": {"$ref": "#/definitions/ipv4-interface-address"}
-                    },
-                    "ipv6": {
-                        "type": "array",
-                        "items": {"$ref": "#/definitions/ipv6-interface-address"}
-                    },
-                    "bundle": {"type": "array"},
-                    "bundle_members": {"type": "array"},
-                    "snmp":  {"$ref": "#/definitions/snmp-info"}
-                },
-                "required": ["name", "description", "ipv4", "ipv6"],
-                "additionalProperties": False
-            },
-            "service-info": {
-                "type": "object",
-                "properties": {
-                    "name": {"type": "string"},
-                    "status": {
-                        "type": "string",
-                        "enum": ["operational", "installed", "planned", "ordered"]
-                    },
-                    "circuit_type": {
-                        "type": "string",
-                        "enum": ["path", "service", "l2circuit"]
-                    },
-                    "project": {"type": "string"}
-                    "equipment": {"type": "string"},
-                    "pop": {"type": "string"},
-                    "pop_abbreviation": {"type": "string"},
-
-                    "other_end_pop": {"type": "string"},
-                    "other_end_pop_abbreviation": {"type": "string"},
-                    "other_end_equipment": {"type": "string"},
-                    "port": {"type": "string"},
-                    "other_end_port": {"type": "string"},
-                    "logical_unit": {
-                        "oneOf": [
-                            {"type": "integer"},
-                            {"type": "string", "maxLength": 0}
-                        ]
-                    },
-                    "other_end_logical_unit": {
-                        "oneOf": [
-                            {"type": "integer"},
-                            {"type": "string", "maxLength": 0}
-                        ]
-                    },
-                    "manufacturer": {
-                        "type": "string",
-                        "enum": ["juniper", "coriant", "infinera",
-                                 "cisco", "hewlett packard",
-                                 "corsa", "graham smith uk ltd",
-                                 "unknown", ""]
-                    },
-                    "card_id": {"type": "string"},
-                    "other_end_card_id": {"type": "string"},
-                    "interface_name": {"type": "string"},
-                    "other_end_interface_name": {"type": "string"},
-
-                    'other_end_pop_name': {"type": "string"},
-                    'pop_name': {"type": "string"}
-                },
-                "additionalProperties": False
-            },
-            "related-service-info": {
-                "type": "object",
-                "properties": {
-                    "name": {"type": "string"},
-                    "status": {
-                        "type": "string",
-                        "enum": ["operational", "installed", "planned", "ordered"]
-                    },
-                    "circuit_type": {
-                        "type": "string",
-                        "enum": ["path", "service", "l2circuit"]
-                    },
-                    "project": {"type": "string"}
-                },
-                "required": ["name", "status", "circuit_type", "project"],
-                "additionalProperties": False
-            }
-        },
-
-        "type": "object",
-        "properties": {
-            "services": {
-                "type": "array",
-                "items": {"$ref": "#/definitions/service-info"}
-            },
-            "interface": {"$ref": "#/definitions/interface-info"},
-            "related-services": {
-                "type": "array",
-                "items": {"$ref": "#/definitions/related-service-info"}
-            },
-            "locations": {"$ref": "#/definitions/locations-list"}
-        },
-        # "required": ["interface"],
-        "additionalProperties": False
-    }
-    ```
-
-    - `locations`: info about the `a` (and `b`, if relevant)
-    ends of the circuit
-    - `services`: info about the service served by this interface
-    - `interface`: relevant interface parameters
-    - `related-services`: all top-level services depending on this interface
-
-
-#### /classifier/peer-info
-
-  * /classifier/peer-info/*`address`*
-
-  The `address` parameter should be the ip address of
-  a remote peer.  If this address is found in the system
-  then information about the interface is returned, otherwise
-  404 is returned (or 422 if the address can't be parsed)
-
-  The response will be formatted according to the following syntax:
-
-    ```json
-    {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-        "type": "object",
-
-        "definitions": {
-            "location-endpoint": {
-                "type": "object",
-                "properties": {
-                    "equipment": {"type": "string"},
-                    "name": {"type": "string"},
-                    "abbreviation": {"type": "string"}
-                },
-                "required": ["equipment", "name", "abbreviation"],
-                "additionalProperties": False
-            },
-            "location": {
-                "type": "object",
-                "properties": {
-                    "a": {"$ref": "#/definitions/location-endpoint"},
-                    "b": {"$ref": "#/definitions/location-endpoint"}
-                },
-                "required": ["a"],
-                "additionalProperties": False
-            },
-            "locations-list": {
-                "type": "array",
-                "items": {"$ref": "#/definitions/location"}
-            },
-            "ip-address": {
-                "type": "string",
-                "oneOf": [
-                    {"pattern": r'^(\d+\.){3}\d+$'},
-                    {"pattern": r'^([a-f\d]{4}:){7}[a-f\d]{4}$'}
-                ]
-            },
-            "interface-address": {
-                "type": "string",
-                "oneOf": [
-                    {"pattern": r'^(\d+\.){3}\d+/\d+$'},
-                    {"pattern": r'^[a-f\d:]+/\d+$'}
-                ]
-            },
-            "vpn-rr-peer": {
-                "type": "object",
-                "properties": {
-                    "name": {"$ref": "#/definitions/ip-address"},
-                    "description": {"type": "string"},
-                    "peer-as": {"type": "integer"},
-                    "router": {"type": "string"}
-                },
-                "required": ["name", "description"],
-                "additionalProperties": False
-            },
-            "ix-public-peer": {
-                "type": "object",
-                "properties": {
-                    "name": {"$ref": "#/definitions/ip-address"},
-                    "description": {"type": "string"},
-                    "router": {"type": "string"},
-                    "as": {
-                        "type": "object",
-                        "properties": {
-                            "local": {"type": "integer"},
-                            "peer": {"type": "integer"},
-                        },
-                        "required": ["local", "peer"],
-                        "additionalProperties": False
-                    }
-                },
-                "required": ["name", "description", "as"],
-                "additionalProperties": False
-            },
-            "ix-public-peer-list": {
-                "type": "array",
-                "items": {"$ref": "#/definitions/ip-address"}
-            },
-            "ix-public-peer-info": {
-                "type": "object",
-                "properties": {
-                    "peer": {"$ref": "#/definitions/ix-public-peer"},
-                    "group": {"$ref": "#/definitions/ix-public-peer-list"},
-                    "router": {"$ref": "#/definitions/ix-public-peer-list"}
-                },
-                "required": ["peer", "group", "router"],
-                "additionalProperties": False
-            },
-            "interface-info": {
-                "type": "object",
-                "properties": {
-                    "name": {"$ref": "#/definitions/ip-address"},
-                    "interface address": {
-                        "$ref": "#/definitions/interface-address"},
-                    "interface name": {"type": "string"},
-                    "router": {"type": "string"}
-                },
-                "required": [
-                    "name", "interface address", "interface name", "router"],
-                "additionalProperties": False
-            },
-            "service-info": {
-                "type": "object"
-            },
-            "interface-lookup-info": {
-                "type": "object",
-                "properties": {
-                    "interface": {"$ref": "#/definitions/interface-info"},
-                    "services": {
-                        "type": "array",
-                        "items": {"$ref": "#/definitions/service-info"}
-                    }
-                }
-            },
-            "snmp-info": {
-                "type": "object",
-                "properties": {
-                    "hostname": {"type": "string"},
-                    "oid": {"type": "string"},
-                    "community": {"type": "string"}
-                },
-                "required": ["oid", "community", "hostname"],
-                "additionalProperties": False
-            },
-            "asn-group-member": {
-                "type": "object",
-                "properties": {
-                    "router": {"type": "string"},
-                    "address": {"type": "string"},
-                    "group": {"type": "string"}
-                },
-                "required": ["router", "address", "group"],
-                "additionalProperties": False
-            },
-            "asn-group": {
-                "type": "object",
-                "properties": {
-                    "asn": {"type": "integer"},
-                    "peers": {
-                        "type": "array",
-                        "items": {"$ref": "#/definitions/asn-group-member"}
-                    }
-                },
-                "required": ["asn", "peers"],
-                "additionalProperties": False
-            }
-
-        },
-
-        "type": "object",
-        "properties": {
-            "ix-public-peer-info": {
-                "$ref": "#/definitions/ix-public-peer-info"},
-            "vpn-rr-peer-info": {"$ref": "#/definitions/vpn-rr-peer"},
-            "interfaces": {
-                "type": "array",
-                "items": {"$ref": "#/definitions/interface-lookup-info"}
-            },
-            "locations": {"$ref": "#/definitions/locations-list"},
-            "snmp": {
-                "type": "array",
-                "items": {"$ref": "#/definitions/snmp-info"}
-            },
-            "asn": {"$ref": "#/definitions/asn-group"}
-        },
-        "additionalProperties": False
-    }
-    ```
-
-    - `locations`: info about the `a` (and `b`, if relevant)
-    ends of the circuit
-    - `interfaces`: interfaces involved on either end and service info
-    - `vpn-rr-peer-info`: peering info
-    - `ix-public-peer-info`: peering info
-
-
-#### /classifier/infinera-lambda-info
-
-  * /classifier/infinera-lambda-info/*`source-equipment`*/*`source-interface`*
-
-  The source-equipment is the equipment that causes the trap, not the NMS that
-  sends it.
-
-  The response will be an object containing the
-  metadata, formatted according to the following schema:
-
-    ```json
-    {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-        "type": "object",
-
-        "definitions": {
-            "location-endpoint": {
-                "type": "object",
-                "properties": {
-                    "equipment": {"type": "string"},
-                    "name": {"type": "string"},
-                    "abbreviation": {"type": "string"}
-                },
-                "required": ["equipment", "name", "abbreviation"],
-                "additionalProperties": False
-            },
-            "location": {
-                "type": "object",
-                "properties": {
-                    "a": {"$ref": "#/definitions/location-endpoint"},
-                    "b": {"$ref": "#/definitions/location-endpoint"}
-                },
-                "required": ["a"],
-                "additionalProperties": False
-            },
-            "locations-list": {
-                "type": "array",
-                "items": {"$ref": "#/definitions/location"}
-            },
-            "service-info": {
-                "type": "object",
-                "properties": {
-                    "id": {"type": "integer"},
-                    "name": {"type": "string"},
-                    "status": {
-                        "type": "string",
-                        "enum": ["operational", "installed", "planned", "ordered"]
-                    },
-                    "circuit_type": {
-                        "type": "string",
-                        "enum": ["path", "service", "l2circuit"]
-                    },
-                    "service_type": {"type": "string"},
-                    "project": {"type": "string"},
-                    "pop_name": {"type": "string"},
-                    "pop_abbreviation": {"type": "string"},
-                    "other_end_pop_name": {"type": "string"},
-                    "other_end_pop_abbreviation": {"type": "string"},
-                    "equipment": {"type": "string"},
-                    "other_end_equipment": {"type": "string"},
-                    "port": {"type": "string"},
-                    "other_end_port": {"type": "string"},
-                    "logical_unit": {
-                        "oneOf": [
-                            {"type": "integer"},
-                            {"type": "string", "maxLength": 0}
-                        ]
-                    },
-                    "other_end_logical_unit": {
-                        "oneOf": [
-                            {"type": "integer"},
-                            {"type": "string", "maxLength": 0}
-                        ]
-                    },
-                    "manufacturer": {
-                        "type": "string",
-                        "enum": ["juniper", "coriant", "infinera",
-                                 "cisco", "hewlett packard",
-                                 "corsa", "graham smith uk ltd",
-                                 "unknown", ""]
-                    },
-                    "card_id": {"type": "string"},
-                    "other_end_card_id": {"type": "string"},
-                    "interface_name": {"type": "string"},
-                    "other_end_interface_name": {"type": "string"}
-                },
-                "additionalProperties": False
-            },
-            "related-service-info": {
-                "type": "object",
-                "properties": {
-                    "name": {"type": "string"},
-                    "status": {
-                        "type": "string",
-                        "enum": ["operational", "installed", "planned", "ordered"]
-                    },
-                    "circuit_type": {
-                        "type": "string",
-                        "enum": ["path", "service", "l2circuit"]
-                    },
-                    "project": {"type": "string"}
-                },
-                "required": ["name", "status", "circuit_type", "project"],
-                "additionalProperties": False
-            },
-            "geant-lambda": {
-                "type": "object",
-                "properties": {
-                    "id": {"type": "integer"},
-                    "name": {"type": "string"},
-                    "project": {"type": "string"},
-                    "status": {
-                        "type": "string",
-                        "enum": ["operational", "installed", "planned", "ordered"]
-                    },
-                },
-                "additionalProperties": False
-            }
-        },
-
-        "type": "object",
-        "properties": {
-            "services": {
-                "type": "array",
-                "items": {"$ref": "#/definitions/service-info"}
-            },
-            "related-services": {
-                "type": "array",
-                "items": {"$ref": "#/definitions/related-service-info"}
-            },
-            "geant-lambda": {
-                "$ref": "#/definitions/geant-lambda"
-            },
-            "locations": {"$ref": "#/definitions/locations-list"}
-        },
-        "additionalProperties": False
-    }
-    ```
-
-    - `locations`: info about the `a` (and `b`, if relevant)
-    ends of the circuit
-    - `services`: info about any services that use this port
-    - `related-services`: any top-level services depending on this circuit
-    - `geant-lambda`: info about the lambda service using this port
-
-
-#### /classifier/coriant-info
-
-  * /classifier/coriant-info/*`equipment name`*/*`entity name`*
-
-  Returns information about the effective path
-  of a coriant card/port combination.
-
-  The response will be formatted according to the following schema:
-
-    ```json
-    {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-        "type": "object",
-
-        "definitions": {
-            "location-endpoint": {
-                "type": "object",
-                "properties": {
-                    "equipment": {"type": "string"},
-                    "name": {"type": "string"},
-                    "abbreviation": {"type": "string"}
-                },
-                "required": ["equipment", "name", "abbreviation"],
-                "additionalProperties": False
-            },
-            "location": {
-                "type": "object",
-                "properties": {
-                    "a": {"$ref": "#/definitions/location-endpoint"},
-                    "b": {"$ref": "#/definitions/location-endpoint"}
-                },
-                "required": ["a"],
-                "additionalProperties": False
-            },
-            "locations-list": {
-                "type": "array",
-                "items": {"$ref": "#/definitions/location"}
-            },
-            "pop-info": {
-                "type": "object",
-                "properties": {
-                    "name": {"type": "string"},
-                    "abbreviation": {"type": "string"},
-                    "country": {"type": "string"},
-                    "city": {"type": "string"},
-                    "longitude": {"type": "number"},
-                    "latitude": {"type": "number"}
-                },
-                "required": [
-                    "name",
-                    "abbreviation",
-                    "country",
-                    "city",
-                    "longitude",
-                    "latitude"
-                ],
-                "additionalProperties": False
-            },
-            "endpoint": {
-                "type": "object",
-                "properties": {
-                    "equipment name": {"type": "string"},
-                    "card id": {"type": "string"},
-                    "port number": {"type": "string"},
-                    "pop": {"$ref": "#/definitions/pop-info"}
-                },
-                "required": ["equipment name", "port number", "pop"],
-                "additionalProperties": False
-            },
-            "path": {
-                "type": "object",
-                "properties": {
-                    'category': {"type": "string"},
-                    'circuit_type': {"type": "string"},
-                    'service_type': {"type": "string"},
-                    'peering_type': {"type": "string"},
-                    'status': {"type": "string"},
-                    'name': {"type": "string"},
-                    'a': {"$ref": "#/definitions/endpoint"},
-                    'b': {"$ref": "#/definitions/endpoint"}
-                },
-                "required": [
-                    "category",
-                    "circuit_type",
-                    "service_type",
-                    "peering_type",
-                    "status",
-                    "a",
-                    "b"],
-                "additionalProperties": False
-            },
-            "related-service-info": {
-                "type": "object",
-                "properties": {
-                    "name": {"type": "string"},
-                    "status": {
-                        "type": "string",
-                        "enum": ["operational", "installed", "planned", "ordered"]
-                    },
-                    "circuit_type": {
-                        "type": "string",
-                        "enum": ["path", "service", "l2circuit"]
-                    },
-                    "project": {"type": "string"}
-                },
-                "additionalProperties": False
-            }
-        },
-
-        "type": "object",
-        "properties": {
-            "equipment name": {"type": "string"},
-            "card id": {"type": "string"},
-            "port number": {"type": "string"},
-            "path": {"$ref": "#/definitions/path"},
-            "related-services": {
-                "type": "array",
-                "items": {"$ref": "#/definitions/related-service-info"}
-            },
-        },
-        "required": ["equipment name", "card id", "port number"],
-        "additionalProperties": False
-    }
-    ```
-
-    - `locations`: info about the `a` and `b` ends of the circuit
-    - `path`: detailed path info (included `a` and `b` ends)
-    - `related-services`: any top-level services depending on this circuit
-
-
-#### /poller/interfaces
-
-  * /poller/interfaces</*`hostname`*>
-
-  The response will be the list of active interfaces on the
-  router `hostname`.
-  Each element of the returned list contains information necessary
-  for setting up snmp throughput counter polling.
-
-  The response will be formatted according to the following schema:
-
-    ```json
-    {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-
-        "definitions": {
-            "service": {
-                "type": "object",
-                "properties": {
-                    "id": {"type": "integer"},
-                    "name": {"type": "string"},
-                    "type": {"type": "string"},
-                    "status": {"type": "string"},
-                },
-                "required": ["id", "name", "type", "status"],
-                "additionalProperties": False
-            },
-            "interface": {
-                "type": "object",
-                "properties": {
-                    "router": {"type": "string"},
-                    "name": {"type": "string"},
-                    "description": {"type": "string"},
-                    "snmp-index": {
-                        "type": "integer",
-                        "minimum": 1
-                    },
-                    "bundle": {
-                        "type": "array",
-                        "items": {"type": "string"}
-                    },
-                    "bundle-parents": {
-                        "type": "array",
-                        "items": {"type": "string"}
-                    },
-                    "circuits": {
-                        "type": "array",
-                        "items": {"$ref": "#/definitions/service"}
-                    }
-                },
-                "required": [
-                    "router", "name", "description",
-                    "snmp-index", "bundle", "bundle-parents",
-                    "circuits"],
-                "additionalProperties": False
-            },
-        },
-
-        "type": "array",
-        "items": {"$ref": "#/definitions/interface"}
-    }
-    ```
-
-#### /lg/routers
-
-  * /lg/routers/*`access`*
-
-  The response will be the list of routers with pop location information.
-  The list will be filtered according to the value of the `access` parameter.
-
-  There are 2 valid values for `access`, these are: `public`, `all`
-
-  The response will be formatted according to the following schema:
-
-    ```json
-    {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-
-        "definitions": {
-            "pop-info": {
-                "type": "object",
-                "properties": {
-                    "name": {"type": "string"},
-                    "abbreviation": {"type": "string"},
-                    "country": {"type": "string"},
-                    "country code": {"type": "string"},
-                    "city": {"type": "string"},
-                    "longitude": {"type": "number"},
-                    "latitude": {"type": "number"}
-                },
-                "required": [
-                    "name",
-                    "abbreviation",
-                    "country",
-                    "country code",
-                    "city",
-                    "longitude",
-                    "latitude"
-                ],
-                "additionalProperties": False
-            },
-            "router": {
-                "type": "object",
-                "properties": {
-                    "equipment name": {"type": "string"},
-                    "type": {
-                        "type": "string",
-                        "enum": ["INTERNAL", "CORE"]
-                    },
-                    "pop": {"$ref": "#/definitions/pop-info"}
-                },
-                "required": ["equipment name", "type", "pop"],
-                "additionalProperties": False
-            }
-        },
-
-        "type": "array",
-        "items": {"$ref": "#/definitions/router"}
-    }
-    ```
-
-
-#### /msr/access-services
-
-  * /msr/access-services
-
-  The response will be the list of services that
-  are of type 'IP Acccess', and will formatted
-  according to the following schema:
-
-    ```json
-    {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-
-        "definitions": {
-            "service": {
-                "type": "object",
-                "properties": {
-                    "id": {"type": "integer"},
-                    "name": {"type": "string"},
-                    "equipment": {"type": "string"},
-                    "pop_name": {"type": "string"},
-                    "other_end_equipment": {"type": "string"},
-                    "other_end_pop_name": {"type": "string"},
-                    "speed_value": {"type": "integer"},
-                    "speed_unit": {"type": "string"}
-                },
-                "required": [
-                    "id", "name",
-                    "pop_name", "equipment",
-                    "other_end_pop_name", "other_end_equipment",
-                    "speed_value", "speed_unit"
-                ],
-                "additionalProperties": False
-            }
-        },
-
-        "type": "array",
-        "items": {"$ref": "#/definitions/service"}
-    }
-    ```
-
-### Testing utilities
-
-The following routes are only available if the server
-was started with the `ENABLE_TESTING_ROUTES` flag.  These routes
-are present only for testing, or for deprectated routes that
-are intended to be deleted.
-
-
-#### /testing/flushdb
-
-  * /testing/flushdb
-
-  This method erases all data in the backend redis
-  database.
-
-#### /testing/infinera-dna-addresses
-#### /testing/coriant-tnms-addresses
-#### /testing/juniper-server-addresses
-
-  * /testing/infinera-dna-addresses
-  * /testing/coriant-tnms-addresses
-  * /testing/juniper-server-addresses
-
-  All of these resources return lists of source addresses
-  of known senders of snmp traps.  Responses will be
-  formatted as follows:
-
-    ```json
-    {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-        "type": "array",
-        "items": {"type": "string"}
-    }
-    ```
-
-
-#### /data/snmp
-
-  * /data/snmp/*`hostname`*
-
-  The response will be a list of information about
-   the interfaces discovered through snmp
-    queries on the requested host
-   and will be formatted as follows:
-
-    ```json
-    {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-        "type": "array",
-        "items": {
-            "type": "object",
-            "properties": {
-                "index": {"type": "string"},
-                "name": {"type": "string"}
-            },
-            "required": ["index", "name"],
-            "additionalProperties": False
-        }
-    }
-    ```
-
-#### /data/bgp
-
-  * /data/bgp/*`hostname`*
-
-  The response will be a list of information about
-   the bgp peerings configured for the requested host
-   and will be formatted as follows:
-
-    ```json
-    {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-        "type": "array",
-        "items": {
-            "type": "object",
-            "properties": {
-                "description": {"type": "string"},
-                "as": {
-                    "type": "object",
-                    "properties": {
-                        "peer": {
-                            "type": "string",
-                            "pattern": r'^\d+$'
-                        },
-                        "local": {
-                            "type": "string",
-                            "pattern": r'^\d+$'
-                        },
-                    },
-                    "required": ["peer", "local"],
-                    "additionalProperties": False
-                },
-            },
-            "required": ["description", "as"],
-            "additionalProperties": False
-        }
-    }
-    ```
-
-## Backend (Redis) Storage Schema
-
-* `netconf:<hostname>`
-
-  * key example
-    * `netconf:mx1.ams.nl.geant.net`
-  * value format
-    * cf. validation in `inventory_provider.juniper.load_config`
-
-* `snmp-interfaces:<hostname>`
-
-  * key example:
-    * `snmp-interfaces:mx1.lon2.uk.geant.net`
-  * value schema
-    TODO: this needs to be checked ...
-    ```json
-    {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-
-        "definitions": {
-            "v4ifc": {
-                "type": "object",
-                "properties": {
-                    "v4Address": {
-                        "type": "string",
-                        "pattern": r'^(\d+\.){3}\d+$'
-                    },
-                    "v4Mask": {
-                        "type": "string",
-                        "pattern": r'^(\d+\.){3}\d+$'
-                    },
-                    "v4InterfaceName": {"type": "string"},
-                    "index": {
-                        "type": "string",
-                        "pattern": r'^\d+$'
-                    }
-                },
-                "required": [
-                    "v4Address", "v4Mask", "v4InterfaceName", "index"],
-                "additionalProperties": False
-            },
-            "v6ifc": {
-                "type": "object",
-                "properties": {
-                    "v6Address": {
-                        "type": "string",
-                        "pattern": r'^[a-f\d:]+$'
-                    },
-                    "v6Mask": {
-                        "type": "string",
-                        "pattern": r'^\d+$'
-                    },
-                    "v6InterfaceName": {"type": "string"},
-                    "index": {
-                        "type": "string",
-                        "pattern": r'^\d+$'
-                    }
-                },
-                "required": [
-                    "v6Address", "v6Mask", "v6InterfaceName", "index"],
-                "additionalProperties": False
-            }
-        },
-
-        "type": "array",
-         "items": {
-            "anyOf": [
-                {"$ref": "#/definitions/v4ifc"},
-                {"$ref": "#/definitions/v6ifc"}
-            ]
-        }
-    }
-    ```
-
-
-* `opsdb:interface_services:<equipment name>:<interface name>`
-
-  * key examples
-    * `opsdb:interface_services:mx1.ams.nl.geant.net:ae15.1103`
-    * `opsdb:interface_services:Lab node 2:1-1/5`
-  * value schema
-    ```json
-    {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-
-        "definitions": {
-            "interface": {
-                "type": "object",
-                "properties": {
-                    "id": {"type": "integer"},
-                    "name": {"type": "string"},
-                    "status": {
-                        "type": "string",
-                        "enum": ["operational", "installed", "planned", "ordered"]
-                    },
-                    "circuit_type": {
-                        "type": "string",
-                        "enum": ["path", "service", "l2circuit"]
-                    },
-                    "service_type": {"type": "string"},
-                    "project": {"type": "string"},
-                    "equipment": {"type": "string"},
-                    "port": {
-                        "anyOf": [
-                            {
-                                "type": "string",
-                                "pattern": r'^[\d:]+$'
-                            },
-                            {
-                                "type": "string",
-                                "enum": ['console']
-                            }
-                        ]
-                    },
-                    "logical_unit": {
-                        "anyOf": [
-                            {"type": "integer"},
-                            {"type": "string", "maxLength": 0}
-                        ]
-                    },
-                    "manufacturer": {
-                        "type": "string",
-                        "enum": ["juniper", "coriant", "infinera",
-                                 "cisco", "hewlett packard",
-                                 "corsa", "graham smith uk ltd",
-                                 "unknown", ""]
-                    },
-                    "card_id": {"type": "string"},
-                    "interface_name": {"type": "string"}
-                },
-                "required": ["id", "name", "status",
-                             "circuit_type", "service_type",
-                             "project", "equipment", "port",
-                             "logical_unit", "manufacturer",
-                             "card_id", "interface_name"],
-                "additionalProperties": False
-            }
-        },
-
-        "type": "object",
-        "patternProperties": {
-            "^.+$": {
-                "type": "array",
-                "items": {"$ref": "#/definitions/interface"}
-            }
-        }
-    }
-    ```
-
-
-* `opsdb:location:<equipment name>`
-
-  * key examples
-    * `opsdb:location:mx1.ams.nl.geant.net`
-    * `opsdb:location:taas.srv1.par.fr.geant.net`
-    * `opsdb:location:A test EX 3-Oct`
-  * value schema
-    ```json
-    {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-        "type": "array",
-        "items": {
-            "type": "object",
-            "properties": {
-                "absid": {"type": "integer"},
-                "equipment_name": {"type": "string"},
-                "pop_name": {"type": "string"},
-                "pop_abbreviation": {"type": "string"},
-                "pop_site_id": {"type": "string"},
-                "country": {"type": "string"},
-                "longitude": {"type": "number"},
-                "latitude": {"type": "number"},
-            },
-            "required": ["absid", "equipment_name",
-                         "pop_name", "pop_abbreviation", "pop_site_id",
-                         "country", "longitude", "latitude"],
-            "additionalProperties": False
-        }
-    }
-    ```
-
-* `opsdb:services:children:<circuit db id>`
-* `opsdb:services:parents:<circuit db id>`
-
-  * key examples
-    * `opsdb:services:children:12363`
-    * `opsdb:services:parents:14407`
-  * value schema
-    TODO: this needs to be checked ...
-    ```json
-    {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-
-        "definitions": {
-            "circuit_relationship": {
-                "type": "object",
-                "properties": {
-                    "parent_circuit": {"type": "string"},
-                    "parent_circuit_id": {"type": "integer"},
-                    "parent_circuit_status": {
-                        "type": "string",
-                        "enum": ["operational", "installed", "planned", "ordered"]
-                    },
-                    "child_circuit": {"type": "string"},
-                    "child_circuit_id": {"type": "integer"},
-                    "child_circuit_status": {
-                        "type": "string",
-                        "enum": ["operational", "installed", "planned", "ordered"]
-                    }
-                    "circuit_group": {"type": "integer"},
-                },
-                "required": [
-                    "parent_circuit",
-                    "parent_circuit_id",
-                    "parent_circuit_status",
-                    "child_circuit",
-                    "child_circuit_id",
-                    "child_circuit_status",
-                    "circuit_group"
-                ],
-                "additionalProperties": False
-            }
-        },
-
-        "type": "object",
-        "patternProperties": {
-            "^\d+$": {
-                "type": "array",
-                "items": {"$ref": "#/definitions/circuit_relationship"}
-            }
-        }
-    }
-    ```
-
-* `alarmsdb:interface_status:<equipment name>:<interface name>`
-  * key examples
-    * `alarmsdb:interface_status:Lab node 1:1-1/3`
-    *  `alarmsdb:interface_status:mx1.ams.nl.geant.net:ae15.1500`
-  * valid values:
-    TODO: verify these values
-    * `unknown`
-    * `up`
-    * `down`
-
-
-* `ix_public_peer:<address>`
-  * key examples
-    * `ix_public_peer:193.203.0.203`
-    * `ix_public_peer:2001:07f8:00a0:0000:0000:5926:0000:0002`
-  * valid values:
-    ```json
-    {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-
-        "definitions": {
-            "ip-address": {
-                "type": "string",
-                "oneOf": [
-                    {"pattern": r'^(\d+\.){3}\d+$'},
-                    {"pattern": r'^([a-f\d]{4}:){7}[a-f\d]{4}$'}
-                ]
-            }
-        },
-
-        "type": "object",
-        "properties": {
-            "name": {"$ref": "#/definitions/ip-address"},
-            "description": {"type": "string"},
-            "as": {
-                "type": "object",
-                "properties": {
-                    "local": {"type": "integer"},
-                    "peer": {"type": "integer"},
-                },
-                "required": ["local", "peer"],
-                "additionalProperties": False
-            }
-        },
-        "required": ["name", "description", "as"],
-        "additionalProperties": False
-    }
-    ```
-
-* `vpn_rr_peers/<address>`
-  * key examples
-    * `vpn_rr_peers:193.203.0.203`
-  * valid values:
-    ```json
-    {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-
-        "definitions": {
-            "ip-address": {
-                "type": "string",
-                "oneOf": [
-                    {"pattern": r'^(\d+\.){3}\d+$'},
-                    {"pattern": r'^([a-f\d]{4}:){7}[a-f\d]{4}$'}
-                ]
-            }
-        },
-
-        "type": "object",
-        "properties": {
-            "name": {"$ref": "#/definitions/ip-address"},
-            "description": {"type": "string"},
-            "peer-as": {"type": "integer"}
-        },
-        "required": ["name", "description"],
-        "additionalProperties": False
-    }
-    ```
-
-* `reverse_interface_addresses/<address>`
-  * key examples
-    * `reverse_interface_addresses:193.203.0.203`
-    * `reverse_interface_addresses:2001:07f8:00a0:0000:0000:5926:0000:0002`
-  * valid values:
-    ```json
-    {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-
-        "definitions": {
-            "v4a": {
-                "type": "string",
-                "pattern": r'^(\d+\.){3}\d+$'
-            },
-            "v6a": {
-                "type": "string",
-                "pattern": r'^([a-f\d]{4}:){7}[a-f\d]{4}$'
-            },
-            "v4i": {
-                "type": "string",
-                "pattern": r'^(\d+\.){3}\d+/\d+$'
-            },
-            "v6i": {
-                "type": "string",
-                "pattern": r'^[a-f\d:]+/\d+$'
-            }
-        },
-
-        "type": "array",
-        "items": {
-            "type": "object",
-            "properties": {
-                "name": {
-                    "oneOf": [
-                        {"$ref": "#/definitions/v4a"},
-                        {"$ref": "#/definitions/v6a"}
-                    ]
-                },
-                "interface address": {
-                    "oneOf": [
-                        {"$ref": "#/definitions/v4i"},
-                        {"$ref": "#/definitions/v6i"}
-                    ]
-                },
-                "interface name": {"type": "string"},
-            },
-            "required": ["name", "interface address", "interface name"],
-            "additionalProperties": False
-        }
-    }
-    ```
+sphinx-build -M html docs/source docs/build
+```
\ No newline at end of file
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..886c27c24903cbd2482cb12c35adcd47038c40eb
--- /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 = 'Inventory Provider'
+copyright = '2021, swd@geant.org'
+author = 'swd@geant.org'
+
+# The full version, including alpha/beta/rc tags
+release = '0.57'
+
+
+# -- 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..3502d074966387065a92af957caaae1a22c2aacd
--- /dev/null
+++ b/docs/source/configuration.rst
@@ -0,0 +1,69 @@
+.. notes and hints about confiuguration and running
+
+Configuration and Running
+=========================
+
+Configuration
+-------------
+
+The module requires two configuration files to be provided
+when launching the web service.
+
+The first configuration file
+must be stored in the environment variable
+`SETTINGS_FILENAME` and is parsed as a Flask configuration
+file.  The file is python and there are two
+custom variables, and adding other Flask-specific
+options is not supported and has not been tested.
+
+* `INVENTORY_PROVIDER_CONFIG_FILENAME`
+
+  * [REQUIRED] Run-time accessible filename
+    of a json file containing the server configuration parameters.  This file
+    must be formatted according to the following json schema
+
+* ENABLE_TESTING_ROUTES
+
+  * [OPTIONAL, default value: False]
+    Boolean flag (can be any value that evaluates to True)
+    to enable routes to special utilities used for testing.
+    *This must never be enabled in a production environment.*
+
+The following is an example of a configuration file.
+
+.. code-block:: python
+
+   INVENTORY_PROVIDER_CONFIG_FILENAME = "/somepath/config.json"
+   ENABLE_TESTING_ROUTES = True
+
+
+The second required file contains Inventory Provider application-level
+configuration parameters.
+
+.. autofunction:: inventory_provider.config.load
+
+
+Running
+--------
+
+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:: bash
+
+     $ export FLASK_APP=app.py
+     $ export SETTINGS_FILENAME=settings.cfg
+     $ flask run
+
+* As an Apache/`mod_wsgi` service.
+
+  * Details of Apache and `mod_wsgi`
+    configuration are beyond the scope of this document.
+
+* As a `gunicorn` wsgi service.
+
+  * Details of `gunicorn` configuration are
+    beyond the scope of this document.
+
diff --git a/docs/source/index.rst b/docs/source/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..0e8a203bbf90d00f7d8c9ffbdf2f6063cd892448
--- /dev/null
+++ b/docs/source/index.rst
@@ -0,0 +1,20 @@
+.. Inventory Provider documentation master file, created by
+   sphinx-quickstart on Sun Jan 24 11:28:52 2021.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Inventory Provider
+==================
+
+Documentation for Inventory Provider.
+This service acts as a single point of truth for
+information about the GÉANT network, exposed by
+by an HTTP API.
+
+
+.. toctree::
+   :maxdepth: 3
+   :caption: Contents:
+
+   configuration
+   protocol/index
\ No newline at end of file
diff --git a/docs/source/protocol/classifier.rst b/docs/source/protocol/classifier.rst
new file mode 100644
index 0000000000000000000000000000000000000000..df7036adbabff8c35b79ac5166333c5ddd26d10d
--- /dev/null
+++ b/docs/source/protocol/classifier.rst
@@ -0,0 +1,35 @@
+.. classifier endpoint docs
+
+
+Classifier Endpoints
+=========================
+
+These endpoints are intended for use by Dashboard V3.
+
+/classifier/peer-info
+---------------------
+
+.. autofunction:: inventory_provider.routes.classifier.get_bgp_peer_info
+
+/classifier/juniper-link-info
+-----------------------------
+
+.. autofunction:: inventory_provider.routes.classifier.get_juniper_link_info
+
+
+/classifier/infinera-lambda-info
+--------------------------------
+
+.. autofunction:: inventory_provider.routes.classifier.get_infinera_lambda_info
+
+
+/classifier/infinera-fiberlink-info
+------------------------------------
+
+.. autofunction::
+   inventory_provider.routes.classifier.get_fiberlink_trap_metadata
+
+/classifier/coriant-info
+------------------------
+
+.. autofunction:: inventory_provider.routes.classifier.get_coriant_info
\ No newline at end of file
diff --git a/docs/source/protocol/data.rst b/docs/source/protocol/data.rst
new file mode 100644
index 0000000000000000000000000000000000000000..72531fd8f43f4c748df910f204b6342c7a0d0d7b
--- /dev/null
+++ b/docs/source/protocol/data.rst
@@ -0,0 +1,22 @@
+.. poller endpoint docs
+
+
+Data Endpoints
+=========================
+
+These endpoints are temporary and will be removed.
+
+/data/routers
+---------------------------------
+
+.. autofunction:: inventory_provider.routes.data.routers
+
+/data/interfaces
+---------------------------------
+
+.. autofunction:: inventory_provider.routes.data.router_interfaces
+
+/data/pop
+---------------------------------
+
+.. autofunction:: inventory_provider.routes.data.equipment_location
diff --git a/docs/source/protocol/index.rst b/docs/source/protocol/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..e0ef1976524f092d255755ee35ba77f4ca5edbaf
--- /dev/null
+++ b/docs/source/protocol/index.rst
@@ -0,0 +1,36 @@
+.. protocol intro
+
+Protocol
+=========================
+
+This module implements a Flask-based webservice which
+communicates with clients over HTTP.
+Responses to valid requests are returned as JSON messages.
+The server will therefore return an error unless
+`application/json` is in the `Accept` request header field.
+
+HTTP communication and JSON grammar details are
+beyond the scope of this document.
+Please refer to [RFC 2616](https://tools.ietf.org/html/rfc2616)
+and www.json.org for more details.
+
+
+/version
+-------------
+
+.. autofunction:: inventory_provider.routes.default.version
+
+
+API modules
+--------------
+
+.. toctree::
+   :maxdepth: 2
+   :caption: Contents:
+
+   classifier
+   poller
+   lg
+   data
+   jobs
+   msr
\ No newline at end of file
diff --git a/docs/source/protocol/jobs.rst b/docs/source/protocol/jobs.rst
new file mode 100644
index 0000000000000000000000000000000000000000..866f98e135cf58dcee02accb6071cd09e7b31e35
--- /dev/null
+++ b/docs/source/protocol/jobs.rst
@@ -0,0 +1,24 @@
+.. poller endpoint docs
+
+
+Jobs Endpoints
+=========================
+
+These endpoints are used for monitoring running jobs.
+
+/jobs/update
+---------------------------------
+
+.. autofunction:: inventory_provider.routes.jobs.update
+
+
+/jobs/check-task-status
+---------------------------------
+
+.. autofunction:: inventory_provider.routes.jobs.check_task_status
+
+
+/jobs/log
+---------------------------------
+
+.. autofunction:: inventory_provider.routes.jobs.load_task_log
diff --git a/docs/source/protocol/lg.rst b/docs/source/protocol/lg.rst
new file mode 100644
index 0000000000000000000000000000000000000000..363555fed626cc07904df9e158db1fa78d7b0d30
--- /dev/null
+++ b/docs/source/protocol/lg.rst
@@ -0,0 +1,12 @@
+.. LG endpoint docs
+
+
+LG Support Endpoints
+=========================
+
+These endpoints are intended for use by LG.
+
+/lg/interfaces
+---------------------------------
+
+.. autofunction:: inventory_provider.routes.lg.routers
diff --git a/docs/source/protocol/msr.rst b/docs/source/protocol/msr.rst
new file mode 100644
index 0000000000000000000000000000000000000000..c73074bb6b5b6bb8ca59e05ff298e4ab5ae01a12
--- /dev/null
+++ b/docs/source/protocol/msr.rst
@@ -0,0 +1,12 @@
+.. poller endpoint docs
+
+
+MSR Support Endpoints
+=========================
+
+These endpoints are intended for use by MSR.
+
+/msr/access-services
+---------------------------------
+
+.. autofunction:: inventory_provider.routes.msr.access_services
diff --git a/docs/source/protocol/poller.rst b/docs/source/protocol/poller.rst
new file mode 100644
index 0000000000000000000000000000000000000000..8bfa6ddc4bb39ece105e3ba0adf93072a22e8355
--- /dev/null
+++ b/docs/source/protocol/poller.rst
@@ -0,0 +1,12 @@
+.. poller endpoint docs
+
+
+BRIAN support Endpoints
+=========================
+
+These endpoints are intended for use by BRIAN.
+
+/poller/interfaces
+---------------------------------
+
+.. autofunction:: inventory_provider.routes.poller.interfaces
diff --git a/inventory_provider/config.py b/inventory_provider/config.py
index aa6ef5ffe623a9037c39fef4bdba1156e22b5ba1..d8ce6e9f5ba1200789cb74ad565c947632f2d6a9 100644
--- a/inventory_provider/config.py
+++ b/inventory_provider/config.py
@@ -1,5 +1,4 @@
 import json
-
 import jsonschema
 
 CONFIG_SCHEMA = {
@@ -142,10 +141,14 @@ CONFIG_SCHEMA = {
 
 def load(f):
     """
-    loads, validates and returns configuration parameters
+    Loads, validates and returns configuration parameters.
+
+    Input is validated against this jsonschema:
+
+    .. asjson:: inventory_provider.config.CONFIG_SCHEMA
 
     :param f: file-like object that produces the config file
-    :return:
+    :return: a dict containing the parsed configuration parameters
     """
     config = json.loads(f.read())
     jsonschema.validate(config, CONFIG_SCHEMA)
diff --git a/inventory_provider/routes/classifier.py b/inventory_provider/routes/classifier.py
index ba79aa3f56bd0dee8b3baf1659710b30fb8e034b..f1bb72de10ed59d583d59be31d8ff2268b3ad1c7 100644
--- a/inventory_provider/routes/classifier.py
+++ b/inventory_provider/routes/classifier.py
@@ -183,6 +183,20 @@ def _link_interface_info(r, hostname, interface):
               methods=['GET', 'POST'])
 @common.require_accepts_json
 def get_juniper_link_info(source_equipment, interface):
+    """
+    Handler for /classifier/juniper-link-info that
+    returns metadata about an IP interface.
+
+    The response will be formatted according to the following schema:
+
+    .. asjson::
+       inventory_provider.routes.classifier_schema.JUNIPER_LINK_RESPONSE_SCHEMA
+
+    :param source_equipment: router hostname
+    :param interface: link interface name
+    :return:
+    """
+
     r = common.get_current_redis()
 
     cache_key = 'classifier-cache:juniper:%s:%s' % (
@@ -472,8 +486,18 @@ def find_interfaces_and_services(address_str):
 
 @routes.route("/peer-info/<address>", methods=['GET', 'POST'])
 @common.require_accepts_json
-def peer_info(address):
+def get_bgp_peer_info(address):
+    """
+    Handler for /classifier/peer-info that returns bgp peering metadata.
 
+    The response will be formatted according to the following schema:
+
+    .. asjson::
+       inventory_provider.routes.classifier_schema.PEER_INFO_RESPONSE_SCHEMA
+
+    :param address: string representation of a bgp peer address
+    :return:
+    """
     # canonicalize the input address first ...
     try:
         obj = ipaddress.ip_address(address)
@@ -541,7 +565,21 @@ def peer_info(address):
               "<source_equipment>/<interface>/<circuit_id>",
               methods=['GET', 'POST'])
 @common.require_accepts_json
-def get_trap_metadata(source_equipment, interface, circuit_id):
+def get_infinera_lambda_info(source_equipment, interface, circuit_id):
+    """
+    Handler for /classifier/infinera-lambda-info that
+    returns metadata for as DTNX port.
+
+    The response will be formatted according to the following schema:
+
+    .. asjson::
+       inventory_provider.routes.classifier_schema.INFINERA_LAMBDA_INFO_RESPONSE_SCHEMA
+
+    :param source_equipment: DTNX name
+    :param address: interface/port name
+    :param circuit_id: infinera circuit id
+    :return:
+    """
 
     interface = interface.replace('-T', '-')
     cache_key = 'classifier-cache:infinera:%s:%s' % (
@@ -595,6 +633,17 @@ def get_trap_metadata(source_equipment, interface, circuit_id):
               methods=['GET', 'POST'])
 @common.require_accepts_json
 def get_fiberlink_trap_metadata(ne_name_str, object_name_str):
+    """
+    Handler for /classifier/infinera-fiberlink-info that
+    returns metadata for a particular opitical path segment.
+
+    TODO: no schema is declared, and there are no validation tests
+
+    :param ne_name_str: OLA or DTNX equipment name
+    :param object_name_str: path name
+    :return:
+    """
+
     objects = object_name_str.split('_')
     shelves = [x.split('-')[0] for x in objects]
     p = r'([a-zA-Z\d]+?-(OLA|DTNX)\d+(-\d)?)'
@@ -665,6 +714,20 @@ def get_fiberlink_trap_metadata(ne_name_str, object_name_str):
               methods=['GET', 'POST'])
 @common.require_accepts_json
 def get_coriant_info(equipment_name, entity_string):
+    """
+    Handler for /classifier/coriant-info that
+    returns metadata for a coriant path.
+
+    The response will be formatted according to the following schema:
+
+    .. asjson::
+       inventory_provider.routes.classifier_schema.CORIANT_INFO_RESPONSE_SCHEMA
+
+    :param source_equipment: grv hostname
+    :param entity_string: path name
+    :return:
+    """
+
     r = common.get_current_redis()
 
     cache_key = 'classifier-cache:coriant:%s:%s' % (
diff --git a/inventory_provider/routes/classifier_schema.py b/inventory_provider/routes/classifier_schema.py
new file mode 100644
index 0000000000000000000000000000000000000000..3d2ca9e1fd986318e751fc95bbe69629f7700059
--- /dev/null
+++ b/inventory_provider/routes/classifier_schema.py
@@ -0,0 +1,554 @@
+
+
+_common_locations_schema_definitions = {
+    "location-endpoint": {
+        "type": "object",
+        "properties": {
+            "equipment": {"type": "string"},
+            "name": {"type": "string"},
+            "abbreviation": {"type": "string"}
+        },
+        "required": ["equipment", "name", "abbreviation"],
+        "additionalProperties": False
+    },
+    "location": {
+        "type": "object",
+        "properties": {
+            "a": {"$ref": "#/definitions/location-endpoint"},
+            "b": {"$ref": "#/definitions/location-endpoint"}
+        },
+        "required": ["a"],
+        "additionalProperties": False
+    },
+    "locations-list": {
+        "type": "array",
+        "items": {"$ref": "#/definitions/location"}
+    }
+}
+
+_juniper_link_response_schema_definitions = {
+    "ip-address": {
+        "type": "string",
+        "oneOf": [
+            {"pattern": r'^(\d+\.){3}\d+$'},
+            {"pattern": r'^([a-f\d]{4}:){7}[a-f\d]{4}$'}
+        ]
+    },
+    "ipv4-interface-address": {
+        "type": "string",
+        "pattern": r'^(\d+\.){3}\d+/\d+$'
+    },
+    "ipv6-interface-address": {
+        "type": "string",
+        "pattern": r'^[a-f\d:]+/\d+$'
+    },
+    "snmp-info": {
+        "type": "object",
+        "properties": {
+            "community": {"type": "string"},
+            "index": {"type": "integer"}
+        },
+        "required": ["community", "index"],
+        "additionalProperties": False
+    },
+    "interface-info": {
+        "type": "object",
+        "properties": {
+            "name": {"type": "string"},
+            "description": {"type": "string"},
+            "ipv4": {
+                "type": "array",
+                "items": {"$ref": "#/definitions/ipv4-interface-address"}
+            },
+            "ipv6": {
+                "type": "array",
+                "items": {"$ref": "#/definitions/ipv6-interface-address"}
+            },
+
+            # TODO: check what's changed: added to make tests pass
+            'bundle': {"type": "array"},
+            'bundle_members': {"type": "array"},
+            'snmp':  {"$ref": "#/definitions/snmp-info"}
+        },
+        "required": ["name", "description", "ipv4", "ipv6"],
+        "additionalProperties": False
+    },
+    "service-info": {
+        "type": "object",
+        "properties": {
+            "id": {"type": "integer"},
+            "name": {"type": "string"},
+            "status": {
+                "type": "string",
+                "enum": ["operational", "installed", "planned", "ordered"]
+            },
+            "circuit_type": {
+                "type": "string",
+                "enum": ["path", "service", "l2circuit"]
+            },
+            "service_type": {"type": "string"},
+            "project": {"type": "string"},
+            "equipment": {"type": "string"},
+            "pop": {"type": "string"},
+            "pop_abbreviation": {"type": "string"},
+
+            "other_end_pop": {"type": "string"},
+            "other_end_pop_abbreviation": {"type": "string"},
+            "other_end_equipment": {"type": "string"},
+            "port": {"type": "string"},
+            "other_end_port": {"type": "string"},
+            "logical_unit": {
+                "oneOf": [
+                    {"type": "integer"},
+                    {"type": "string", "maxLength": 0}
+                ]
+            },
+            "other_end_logical_unit": {
+                "oneOf": [
+                    {"type": "integer"},
+                    {"type": "string", "maxLength": 0}
+                ]
+            },
+            "manufacturer": {
+                "type": "string",
+                "enum": ["juniper", "coriant", "infinera",
+                         "cisco", "hewlett packard",
+                         "corsa", "graham smith uk ltd",
+                         "unknown", ""]
+            },
+            "card_id": {"type": "string"},
+            "other_end_card_id": {"type": "string"},
+            "interface_name": {"type": "string"},
+            "other_end_interface_name": {"type": "string"},
+
+            # TODO: check what's changed: added to make tests pass
+            'other_end_pop_name': {"type": "string"},
+            'pop_name': {"type": "string"}
+        },
+        # TODO: modify service-info so that "" entries are just omitted
+        #       (... rather than requiring 'oneOf')
+        # TODO: put 'other_end_*' params in a sub dictionary
+        # "required": [
+        #     "id", "name", "status",
+        #     "circuit_type", "service_type",
+        #     "project", "port", "manufacturer",
+        #     "equipment", "logical_unit", "card_id", "interface_name"
+        # ],
+        "additionalProperties": False
+    },
+    "related-service-info": {
+        "type": "object",
+        "properties": {
+            "name": {"type": "string"},
+            "status": {
+                "type": "string",
+                "enum": ["operational", "installed", "planned", "ordered"]
+            },
+            "circuit_type": {
+                "type": "string",
+                "enum": ["path", "service", "l2circuit"]
+            },
+            "project": {"type": "string"}
+        },
+        "required": ["name", "status", "circuit_type", "project"],
+        "additionalProperties": False
+    }
+}
+
+JUNIPER_LINK_RESPONSE_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "type": "object",
+
+    "definitions": {
+        **_juniper_link_response_schema_definitions,
+        **_common_locations_schema_definitions
+    },
+
+    "type": "object",
+    "properties": {
+        "services": {
+            "type": "array",
+            "items": {"$ref": "#/definitions/service-info"}
+        },
+        "interface": {"$ref": "#/definitions/interface-info"},
+        "related-services": {
+            "type": "array",
+            "items": {"$ref": "#/definitions/related-service-info"}
+        },
+        "locations": {"$ref": "#/definitions/locations-list"}
+    },
+    "required": ["interface", "locations"],
+    "additionalProperties": False
+}
+
+
+_peer_info_response_schema_definitions = {
+    "ip-address": {
+        "type": "string",
+        "oneOf": [
+            {"pattern": r'^(\d+\.){3}\d+$'},
+            {"pattern": r'^([a-f\d]{4}:){7}[a-f\d]{4}$'}
+        ]
+    },
+    "interface-address": {
+        "type": "string",
+        "oneOf": [
+            {"pattern": r'^(\d+\.){3}\d+/\d+$'},
+            {"pattern": r'^[a-f\d:]+/\d+$'}
+        ]
+    },
+    "vpn-rr-peer": {
+        "type": "object",
+        "properties": {
+            "name": {"$ref": "#/definitions/ip-address"},
+            "description": {"type": "string"},
+            "peer-as": {"type": "integer"},
+            "router": {"type": "string"}
+        },
+        "required": ["name", "description"],
+        "additionalProperties": False
+    },
+    "ix-public-peer": {
+        "type": "object",
+        "properties": {
+            "name": {"$ref": "#/definitions/ip-address"},
+            "description": {"type": "string"},
+            "router": {"type": "string"},
+            "as": {
+                "type": "object",
+                "properties": {
+                    "local": {"type": "integer"},
+                    "peer": {"type": "integer"},
+                },
+                "required": ["local", "peer"],
+                "additionalProperties": False
+            }
+        },
+        "required": ["name", "description", "as"],
+        "additionalProperties": False
+    },
+    "ix-public-peer-list": {
+        "type": "array",
+        "items": {"$ref": "#/definitions/ip-address"}
+    },
+    "ix-public-peer-info": {
+        "type": "object",
+        "properties": {
+            "peer": {"$ref": "#/definitions/ix-public-peer"},
+            "group": {"$ref": "#/definitions/ix-public-peer-list"},
+            "router": {"$ref": "#/definitions/ix-public-peer-list"}
+        },
+        "required": ["peer", "group", "router"],
+        "additionalProperties": False
+    },
+    "interface-info": {
+        "type": "object",
+        "properties": {
+            "name": {"$ref": "#/definitions/ip-address"},
+            "interface address": {
+                "$ref": "#/definitions/interface-address"},
+            "interface name": {"type": "string"},
+            "router": {"type": "string"}
+        },
+        "required": [
+            "name", "interface address", "interface name", "router"],
+        "additionalProperties": False
+    },
+    "service-info": {
+        "type": "object"
+    },
+    "interface-lookup-info": {
+        "type": "object",
+        "properties": {
+            "interface": {"$ref": "#/definitions/interface-info"},
+            "services": {
+                "type": "array",
+                "items": {"$ref": "#/definitions/service-info"}
+            }
+        }
+    },
+    "snmp-info": {
+        "type": "object",
+        "properties": {
+            "hostname": {"type": "string"},
+            "oid": {"type": "string"},
+            "community": {"type": "string"}
+        },
+        "required": ["oid", "community", "hostname"],
+        "additionalProperties": False
+    },
+    "asn-group-member": {
+        "type": "object",
+        "properties": {
+            "router": {"type": "string"},
+            "address": {"type": "string"},
+            "group": {"type": "string"}
+        },
+        "required": ["router", "address", "group"],
+        "additionalProperties": False
+    },
+    "asn-group": {
+        "type": "object",
+        "properties": {
+            "asn": {"type": "integer"},
+            "peers": {
+                "type": "array",
+                "items": {"$ref": "#/definitions/asn-group-member"}
+            }
+        },
+        "required": ["asn", "peers"],
+        "additionalProperties": False
+    }
+}
+
+
+PEER_INFO_RESPONSE_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "type": "object",
+
+    "definitions": {
+        **_peer_info_response_schema_definitions,
+        **_common_locations_schema_definitions
+    },
+
+    "type": "object",
+    "properties": {
+        "ix-public-peer-info": {
+            "$ref": "#/definitions/ix-public-peer-info"},
+        "vpn-rr-peer-info": {"$ref": "#/definitions/vpn-rr-peer"},
+        "interfaces": {
+            "type": "array",
+            "items": {"$ref": "#/definitions/interface-lookup-info"}
+        },
+        "locations": {"$ref": "#/definitions/locations-list"},
+        "snmp": {
+            "type": "array",
+            "items": {"$ref": "#/definitions/snmp-info"}
+        },
+        "asn": {"$ref": "#/definitions/asn-group"}
+    },
+    "additionalProperties": False
+}
+
+_infinera_lambda_response_schema_definitions = {
+    "service-info": {
+        "type": "object",
+        "properties": {
+            "id": {"type": "integer"},
+            "name": {"type": "string"},
+            "status": {
+                "type": "string",
+                "enum": ["operational", "installed", "planned", "ordered"]
+            },
+            "circuit_type": {
+                "type": "string",
+                "enum": ["path", "service", "l2circuit"]
+            },
+            "service_type": {"type": "string"},
+            "project": {"type": "string"},
+            "pop_name": {"type": "string"},
+            "pop_abbreviation": {"type": "string"},
+            "other_end_pop_name": {"type": "string"},
+            "other_end_pop_abbreviation": {"type": "string"},
+            "equipment": {"type": "string"},
+            "other_end_equipment": {"type": "string"},
+            "port": {"type": "string"},
+            "other_end_port": {"type": "string"},
+            "logical_unit": {
+                "oneOf": [
+                    {"type": "integer"},
+                    {"type": "string", "maxLength": 0}
+                ]
+            },
+            "other_end_logical_unit": {
+                "oneOf": [
+                    {"type": "integer"},
+                    {"type": "string", "maxLength": 0}
+                ]
+            },
+            "manufacturer": {
+                "type": "string",
+                "enum": ["juniper", "coriant", "infinera",
+                         "cisco", "hewlett packard",
+                         "corsa", "graham smith uk ltd",
+                         "unknown", ""]
+            },
+            "card_id": {"type": "string"},
+            "other_end_card_id": {"type": "string"},
+            "interface_name": {"type": "string"},
+            "other_end_interface_name": {"type": "string"}
+        },
+        "additionalProperties": False
+    },
+    "related-service-info": {
+        "type": "object",
+        "properties": {
+            "name": {"type": "string"},
+            "status": {
+                "type": "string",
+                "enum": ["operational", "installed", "planned", "ordered"]
+            },
+            "circuit_type": {
+                "type": "string",
+                "enum": ["path", "service", "l2circuit"]
+            },
+            "project": {"type": "string"}
+        },
+        "required": ["name", "status", "circuit_type", "project"],
+        "additionalProperties": False
+    },
+    "geant-lambda": {
+        "type": "object",
+        "properties": {
+            "id": {"type": "integer"},
+            "name": {"type": "string"},
+            "project": {"type": "string"},
+            "status": {
+                "type": "string",
+                "enum": ["operational", "installed", "planned", "ordered"]
+            },
+        },
+        "additionalProperties": False
+    }
+}
+
+INFINERA_LAMBDA_INFO_RESPONSE_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "type": "object",
+
+    "definitions": {
+        **_infinera_lambda_response_schema_definitions,
+        **_common_locations_schema_definitions
+    },
+
+    "type": "object",
+    "properties": {
+        "services": {
+            "type": "array",
+            "items": {"$ref": "#/definitions/service-info"}
+        },
+        "related-services": {
+            "type": "array",
+            "items": {"$ref": "#/definitions/related-service-info"}
+        },
+        "geant-lambda": {
+            "$ref": "#/definitions/geant-lambda"
+        },
+        "locations": {"$ref": "#/definitions/locations-list"}
+    },
+    "additionalProperties": False
+}
+
+CORIANT_INFO_RESPONSE_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "type": "object",
+
+    "definitions": {
+        "location-endpoint": {
+            "type": "object",
+            "properties": {
+                "equipment": {"type": "string"},
+                "name": {"type": "string"},
+                "abbreviation": {"type": "string"}
+            },
+            "required": ["equipment", "name", "abbreviation"],
+            "additionalProperties": False
+        },
+        "location": {
+            "type": "object",
+            "properties": {
+                "a": {"$ref": "#/definitions/location-endpoint"},
+                "b": {"$ref": "#/definitions/location-endpoint"}
+            },
+            "required": ["a"],
+            "additionalProperties": False
+        },
+        "locations-list": {
+            "type": "array",
+            "items": {"$ref": "#/definitions/location"}
+        },
+        "pop-info": {
+            "type": "object",
+            "properties": {
+                "name": {"type": "string"},
+                "abbreviation": {"type": "string"},
+                "country": {"type": "string"},
+                "city": {"type": "string"},
+                "longitude": {"type": "number"},
+                "latitude": {"type": "number"}
+            },
+            "required": [
+                "name",
+                "abbreviation",
+                "country",
+                "city",
+                "longitude",
+                "latitude"
+            ],
+            "additionalProperties": False
+        },
+        "endpoint": {
+            "type": "object",
+            "properties": {
+                "equipment name": {"type": "string"},
+                "card id": {"type": "string"},
+                "port number": {"type": "string"},
+                "pop": {"$ref": "#/definitions/pop-info"}
+            },
+            "required": ["equipment name", "port number", "pop"],
+            "additionalProperties": False
+        },
+        "path": {
+            "type": "object",
+            "properties": {
+                'category': {"type": "string"},
+                'circuit_type': {"type": "string"},
+                'service_type': {"type": "string"},
+                'peering_type': {"type": "string"},
+                'status': {"type": "string"},
+                'name': {"type": "string"},
+                'a': {"$ref": "#/definitions/endpoint"},
+                'b': {"$ref": "#/definitions/endpoint"}
+            },
+            "required": [
+                "category",
+                "circuit_type",
+                "service_type",
+                "peering_type",
+                "status",
+                "a",
+                "b"],
+            "additionalProperties": False
+        },
+        "related-service-info": {
+            "type": "object",
+            "properties": {
+                "name": {"type": "string"},
+                "status": {
+                    "type": "string",
+                    "enum": ["operational", "installed", "planned", "ordered"]
+                },
+                "circuit_type": {
+                    "type": "string",
+                    "enum": ["path", "service", "l2circuit"]
+                },
+                "project": {"type": "string"}
+            },
+            "additionalProperties": False
+        }
+    },
+
+    "type": "object",
+    "properties": {
+        "equipment name": {"type": "string"},
+        "card id": {"type": "string"},
+        "port number": {"type": "string"},
+        "path": {"$ref": "#/definitions/path"},
+        "related-services": {
+            "type": "array",
+            "items": {"$ref": "#/definitions/related-service-info"}
+        },
+    },
+    "required": ["equipment name", "card id", "port number"],
+    "additionalProperties": False
+}
diff --git a/inventory_provider/routes/data.py b/inventory_provider/routes/data.py
index f25bd80dae5374c1b43b3802e697a07327b6c137..9c9842d9ec2e32b921b585f8d5a66d7032934552 100644
--- a/inventory_provider/routes/data.py
+++ b/inventory_provider/routes/data.py
@@ -9,6 +9,80 @@ from inventory_provider.db import opsdb
 
 routes = Blueprint("inventory-data-query-routes", __name__)
 
+ROUTERS_RESPONSE_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "type": "array",
+    "items": {"type": "string"}
+}
+
+ROUTER_INTERFACES_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "type": "array",
+    "items": {
+        "type": "object",
+        "properties": {
+            "name": {"type": "string"},
+            "router": {"type": "string"},
+            "description": {"type": "string"},
+            "ipv4": {
+                "type": "array",
+                "items": {"type": "string"}
+            },
+            "ipv6": {
+                "type": "array",
+                "items": {"type": "string"}
+            }
+        },
+        "required": ["name", "description", "router", "ipv4", "ipv6"],
+        "additionalProperties": False
+    }
+}
+
+POP_RESPONSE_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-07/schema#",
+
+    "definitions": {
+        "pop-info": {
+            "type": "object",
+            "properties": {
+                "name": {"type": "string"},
+                "abbreviation": {"type": "string"},
+                "country": {"type": "string"},
+                "city": {"type": "string"},
+                "longitude": {"type": "number"},
+                "latitude": {"type": "number"}
+            },
+            "required": [
+                "name",
+                "abbreviation",
+                "country",
+                "city",
+                "longitude",
+                "latitude"
+            ],
+            "additionalProperties": False
+        },
+        "equipment-info": {
+            "type": "object",
+            "properties": {
+                'equipment-name': {"type": "string"},
+                'status': {"type": "string"},
+                'pop': {"$ref": "#/definitions/pop-info"}
+            },
+            "required": [
+                "equipment-name",
+                "status",
+                "pop"
+            ],
+            "additionalProperties": False
+
+        }
+    },
+
+    "type": "array",
+    "items": {"$ref": "#/definitions/equipment-info"}
+}
+
 
 @routes.after_request
 def after_request(resp):
@@ -18,6 +92,17 @@ def after_request(resp):
 @routes.route("/routers", methods=['GET', 'POST'])
 @common.require_accepts_json
 def routers():
+    """
+    Handler for `/data/routers`.
+
+    The response will be a list of router hostnames
+    for which information is available and will formatted
+    according to the following schema:
+
+    .. asjson:: inventory_provider.routes.data.ROUTERS_RESPONSE_SCHEMA
+
+    :return:
+    """
     r = common.get_current_redis()
     result = []
     for k in r.keys('netconf:*'):
@@ -31,6 +116,18 @@ def routers():
 @routes.route("/interfaces/<hostname>", methods=['GET', 'POST'])
 @common.require_accepts_json
 def router_interfaces(hostname=None):
+    """
+    Handler for `/data/interfaces</hostname>`.
+
+    The response will be a list of information about
+    the interfaces present on the requested host
+    and will be formatted as follows:
+
+    .. asjson:: inventory_provider.routes.data.ROUTER_INTERFACES_SCHEMA
+
+    :param hostname: optional hostname
+    :return:
+    """
 
     cache_key = f'classifier-cache:netconf-interfaces:{hostname}' \
         if hostname else 'classifier-cache:netconf-interfaces:all'
@@ -67,6 +164,18 @@ def router_interfaces(hostname=None):
 @routes.route("/pop/<equipment_name>", methods=['GET', 'POST'])
 @common.require_accepts_json
 def equipment_location(equipment_name):
+    """
+    Handler for `/data/pop/[equipment name]`.
+
+    Returns location information for the equipment identified
+    by `equipment name` and will be formatted as follows:
+
+    .. asjson:: inventory_provider.routes.data.POP_RESPONSE_SCHEMA
+
+    :param equipment_name: equipment name
+    :return:
+    """
+
     config = current_app.config['INVENTORY_PROVIDER_CONFIG']
 
     with db.connection(config['ops-db']) as cx:
diff --git a/inventory_provider/routes/default.py b/inventory_provider/routes/default.py
index d7f1e897ed91f982a69b6a1d56f29f5fac471cad..f65d6124835c09f70459f9d2defe01a36e23dbd6 100644
--- a/inventory_provider/routes/default.py
+++ b/inventory_provider/routes/default.py
@@ -8,6 +8,40 @@ routes = Blueprint("inventory-data-default-routes", __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"},
+            },
+            "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
+}
+
 
 @routes.after_request
 def after_request(resp):
@@ -17,6 +51,15 @@ def after_request(resp):
 @routes.route("/version", methods=['GET', 'POST'])
 @common.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:: inventory_provider.routes.default.VERSION_SCHEMA
+
+    :return:
+    """
     config = current_app.config["INVENTORY_PROVIDER_CONFIG"]
     version_params = {
         'api': API_VERSION,
diff --git a/inventory_provider/routes/jobs.py b/inventory_provider/routes/jobs.py
index af879679526263d59d14093f99f149bb80a47169..00f6a6a2ec746fc4d40cd249b93c570eccdc63f7 100644
--- a/inventory_provider/routes/jobs.py
+++ b/inventory_provider/routes/jobs.py
@@ -13,6 +13,70 @@ routes = Blueprint("inventory-data-job-routes", __name__)
 logger = logging.getLogger(__name__)
 
 
+TASK_ID_RESPONSE_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "type": "object",
+    "properties": {
+        "task id": {"type": "string"}
+    },
+    "required": ["task id"],
+    "additionalProperties": False
+}
+
+TASK_LOG_RESPONSE_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "type": "object",
+    "properties": {
+      "pending": {"type": "array", "items": {"type": "string"}},
+      "errors": {"type": "array", "items": {"type": "string"}},
+      "failed": {"type": "array", "items": {"type": "string"}},
+      "warnings": {"type": "array", "items": {"type": "string"}},
+    },
+    "required": ["pending", "errors", "failed", "warnings"],
+    "additionalProperties": False
+}
+
+INDIVIDUAL_TASK_STATUS_RESPONSE_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "definitions": {
+        "task": {
+            "type": "object",
+            "properties": {
+                "id": {"type": "string"},
+                "status": {"type": "string"},
+                "exception": {"type": "boolean"},
+                "ready": {"type": "boolean"},
+                "success": {"type": "boolean"},
+                "result": {"type": ["object", "null"]},
+                "parent": {"type": ["string", "null"]}
+            },
+            "required": [
+                "id", "status", "exception", "ready", "success", "parent"],
+            "additionalProperties": False
+        }
+    },
+
+    "type": "array",
+    "items": {"$ref": "#/definitions/task"}
+}
+
+
+# INDIVIDUAL_TASK_STATUS_RESPONSE_SCHEMA = {
+#     "$schema": "http://json-schema.org/draft-07/schema#",
+#     "type": "object",
+#     "properties": {
+#         "id": {"type": "string"},
+#         "status": {"type": "string"},
+#         "exception": {"type": "boolean"},
+#         "ready": {"type": "boolean"},
+#         "success": {"type": "boolean"},
+#         "result": {"type": "object"}
+#     },
+#     "required": ["id", "status", "exception", "ready", "success"],
+#     "additionalProperties": False
+# }
+
+
 @routes.after_request
 def after_request(resp):
     return common.after_request(resp)
@@ -21,7 +85,19 @@ def after_request(resp):
 @routes.route("/update", methods=['GET', 'POST'])
 @common.require_accepts_json
 def update():
+    """
+    Handler for `/jobs/update`.
+
+    This resource updates the inventory network data for juniper devices.
+    The function completes asynchronously and a list of outstanding
+    task id's is returned so the caller can
+    use `/jobs/check-task-status` to determine when all jobs
+    are finished.  The response will be formatted as follows:
 
+    .. asjson:: inventory_provider.routes.jobs.TASK_ID_RESPONSE_SCHEMA
+
+    :return:
+    """
     force = request.args.get('force', default='false', type=str)
     try:
         force = strtobool(force)
@@ -55,6 +131,20 @@ def reload_router_config(equipment_name):
 @routes.route("check-task-status/<task_id>", methods=['GET', 'POST'])
 @common.require_accepts_json
 def check_task_status(task_id):
+    """
+    Handler for /jobs/check-task-status/*`task-id`*
+
+    This resource returns the current status of
+    an asynchronous task started by `/jobs/update`
+    or `jobs/reload-router-config`.  The return value
+    will be formatted as follows:
+
+    .. asjson::
+       inventory_provider.routes.jobs.INDIVIDUAL_TASK_STATUS_RESPONSE_SCHEMA
+
+    :param task_id:
+    :return:
+    """
     return jsonify(list(worker.check_task_status(task_id)))
 
 
@@ -76,7 +166,18 @@ def check_update_status():
 @routes.route("log", methods=['GET', 'POST'])
 @common.require_accepts_json
 def load_task_log():
+    """
+    Handler for `/jobs/log`.
+
+    This resource returns the state of the previous (or current)
+    tasks associated with a call to `/jobs/update`.  The response
+    contains error or warning messages, if any were generated, and
+    will be formatted according to the following schema:
+
+    .. asjson:: inventory_provider.routes.jobs.TASK_LOG_RESPONSE_SCHEMA
 
+    :return:
+    """
     FINALIZATION_EVENTS = {'task-succeeded', 'task-failed', 'task-revoked'}
 
     config = current_app.config['INVENTORY_PROVIDER_CONFIG']
diff --git a/inventory_provider/routes/lg.py b/inventory_provider/routes/lg.py
index bcb528ccc8252f806c3ce5b8b6366dea7e8da0f8..af1903837ae83e2d3d405b7eb9b470c47e5c357f 100644
--- a/inventory_provider/routes/lg.py
+++ b/inventory_provider/routes/lg.py
@@ -10,6 +10,52 @@ ACCESS_PUBLIC = 'public'
 ACCESS_INTERNAL = 'all'
 
 
+LG_ROUTERS_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-07/schema#",
+
+    "definitions": {
+        "pop-info": {
+            "type": "object",
+            "properties": {
+                "name": {"type": "string"},
+                "abbreviation": {"type": "string"},
+                "country": {"type": "string"},
+                "country code": {"type": "string"},
+                "city": {"type": "string"},
+                "longitude": {"type": "number"},
+                "latitude": {"type": "number"}
+            },
+            "required": [
+                "name",
+                "abbreviation",
+                "country",
+                "country code",
+                "city",
+                "longitude",
+                "latitude"
+            ],
+            "additionalProperties": False
+        },
+        "router": {
+            "type": "object",
+            "properties": {
+                "equipment name": {"type": "string"},
+                "type": {
+                    "type": "string",
+                    "enum": ["INTERNAL", "CORE"]
+                },
+                "pop": {"$ref": "#/definitions/pop-info"}
+            },
+            "required": ["equipment name", "type", "pop"],
+            "additionalProperties": False
+        }
+    },
+
+    "type": "array",
+    "items": {"$ref": "#/definitions/router"}
+}
+
+
 @routes.after_request
 def after_request(resp):
     return common.after_request(resp)
@@ -18,6 +64,22 @@ def after_request(resp):
 @routes.route("/routers/<string:access>", methods=['GET', 'POST'])
 @common.require_accepts_json
 def routers(access):
+    """
+    Handler for `/lg/routers/[access]` that returns a list of
+    router information for use by LG.
+
+    Endpoints `/lg/routers/public` and `/lg/routers/all`
+    are supported, and only publicly accessible routers
+    or all routers, respectively, are returned.
+
+    The response will be formatted according to the following schema:
+
+    .. asjson::
+       inventory_provider.routes.lg.LG_ROUTERS_SCHEMA
+
+    :param access: one of `public` or `all`
+    :return:
+    """
 
     if access not in {ACCESS_INTERNAL, ACCESS_PUBLIC}:
         return Response(
diff --git a/inventory_provider/routes/msr.py b/inventory_provider/routes/msr.py
index d109d656a51f83d2a3299ba6fa1f8d90a355d5f0..ce17ae63f3c5495d0ff38d189a42bbdddd663158 100644
--- a/inventory_provider/routes/msr.py
+++ b/inventory_provider/routes/msr.py
@@ -7,6 +7,37 @@ from inventory_provider.routes import common
 routes = Blueprint("msr-query-routes", __name__)
 
 
+ACCESS_SERVICES_LIST_SCHEMA = {
+    "$schema": "http://json-schema.org/draft-07/schema#",
+
+    "definitions": {
+        "service": {
+            "type": "object",
+            "properties": {
+                "id": {"type": "integer"},
+                "name": {"type": "string"},
+                "equipment": {"type": "string"},
+                "pop_name": {"type": "string"},
+                "other_end_equipment": {"type": "string"},
+                "other_end_pop_name": {"type": "string"},
+                "speed_value": {"type": "integer"},
+                "speed_unit": {"type": "string"}
+            },
+            "required": [
+                "id", "name",
+                "pop_name", "equipment",
+                "other_end_pop_name", "other_end_equipment",
+                "speed_value", "speed_unit"
+            ],
+            "additionalProperties": False
+        }
+    },
+
+    "type": "array",
+    "items": {"$ref": "#/definitions/service"}
+}
+
+
 @routes.after_request
 def after_request(resp):
     return common.after_request(resp)
@@ -15,7 +46,18 @@ def after_request(resp):
 @routes.route("/access-services", methods=['GET', 'POST'])
 @common.require_accepts_json
 def access_services():
+    """
+    Handler for `/msr/access-services`.
+
+    This method is in development, not yet used.
+
+    The response will be formatted according to the following schema:
+
+    .. asjson::
+       inventory_provider.routes.msr.ACCESS_SERVICES_LIST_SCHEMA
 
+    :return:
+    """
     redis = common.get_current_redis()
 
     def _services():
diff --git a/inventory_provider/routes/poller.py b/inventory_provider/routes/poller.py
index f51067f14762f96e2a91eb07f9a7e8f08868d9ca..228ae0b7772f27f2dd4484a03af68984c81b6b6f 100644
--- a/inventory_provider/routes/poller.py
+++ b/inventory_provider/routes/poller.py
@@ -9,6 +9,56 @@ from inventory_provider.routes import common
 logger = logging.getLogger(__name__)
 routes = Blueprint('poller-support-routes', __name__)
 
+INTERFACE_LIST_SCHEMA = {
+    '$schema': 'http://json-schema.org/draft-07/schema#',
+
+    'definitions': {
+        'service': {
+            'type': 'object',
+            'properties': {
+                'id': {'type': 'integer'},
+                'name': {'type': 'string'},
+                'type': {'type': 'string'},
+                'status': {'type': 'string'},
+            },
+            'required': ['id', 'name', 'type', 'status'],
+            'additionalProperties': False
+        },
+        'interface': {
+            'type': 'object',
+            'properties': {
+                'router': {'type': 'string'},
+                'name': {'type': 'string'},
+                'description': {'type': 'string'},
+                'snmp-index': {
+                    'type': 'integer',
+                    'minimum': 1
+                },
+                'bundle': {
+                    'type': 'array',
+                    'items': {'type': 'string'}
+                },
+                'bundle-parents': {
+                    'type': 'array',
+                    'items': {'type': 'string'}
+                },
+                'circuits': {
+                    'type': 'array',
+                    'items': {'$ref': '#/definitions/service'}
+                }
+            },
+            'required': [
+                'router', 'name', 'description',
+                'snmp-index', 'bundle', 'bundle-parents',
+                'circuits'],
+            'additionalProperties': False
+        },
+    },
+
+    'type': 'array',
+    'items': {'$ref': '#/definitions/interface'}
+}
+
 
 @routes.after_request
 def after_request(resp):
@@ -137,7 +187,23 @@ def _load_poller_interfaces(hostname=None):
 @routes.route("/interfaces", methods=['GET', 'POST'])
 @routes.route('/interfaces/<hostname>', methods=['GET', 'POST'])
 @common.require_accepts_json
-def poller_interface_oids(hostname=None):
+def interfaces(hostname=None):
+    """
+    Handler for `/poller/interfaces` and
+    `/poller/interfaces/<hostname>`
+    which returns information for either all interfaces
+    or those on the requested hostname.
+
+    The response is a list of information for all
+    interfaces that should be polled, including service
+    information and snmp information.
+
+    .. asjson::
+       inventory_provider.routes.poller.INTERFACE_LIST_SCHEMA
+
+    :param hostname: optional, if present should be a router hostname
+    :return:
+    """
 
     cache_key = f'classifier-cache:poller-interfaces:{hostname}' \
         if hostname else 'classifier-cache:poller-interfaces:all'
diff --git a/requirements.txt b/requirements.txt
index 73ee0639eb07da9a58b076055cece5a86d79979e..3c1a2c7e14fc1376ef5076c55587b65c3120549d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -15,3 +15,5 @@ netifaces
 pytest
 pytest-mock
 responses
+sphinx
+sphinx-rtd-theme
diff --git a/test/per_router/test_poller_routes.py b/test/per_router/test_poller_routes.py
index 07acce195af902dcd3b6ae40c2477d6674868e4b..d7351c09dd899ab785cf73568a2039c8c96a0d10 100644
--- a/test/per_router/test_poller_routes.py
+++ b/test/per_router/test_poller_routes.py
@@ -1,61 +1,12 @@
 import json
 import jsonschema
+from inventory_provider.routes.poller import INTERFACE_LIST_SCHEMA
 
 DEFAULT_REQUEST_HEADERS = {
     "Content-type": "application/json",
     "Accept": ["application/json"]
 }
 
-INTERFACE_LIST_SCHEMA = {
-    '$schema': 'http://json-schema.org/draft-07/schema#',
-
-    'definitions': {
-        'service': {
-            'type': 'object',
-            'properties': {
-                'id': {'type': 'integer'},
-                'name': {'type': 'string'},
-                'type': {'type': 'string'},
-                'status': {'type': 'string'},
-            },
-            'required': ['id', 'name', 'type', 'status'],
-            'additionalProperties': False
-        },
-        'interface': {
-            'type': 'object',
-            'properties': {
-                'router': {'type': 'string'},
-                'name': {'type': 'string'},
-                'description': {'type': 'string'},
-                'snmp-index': {
-                    'type': 'integer',
-                    'minimum': 1
-                },
-                'bundle': {
-                    'type': 'array',
-                    'items': {'type': 'string'}
-                },
-                'bundle-parents': {
-                    'type': 'array',
-                    'items': {'type': 'string'}
-                },
-                'circuits': {
-                    'type': 'array',
-                    'items': {'$ref': '#/definitions/service'}
-                }
-            },
-            'required': [
-                'router', 'name', 'description',
-                'snmp-index', 'bundle', 'bundle-parents',
-                'circuits'],
-            'additionalProperties': False
-        },
-    },
-
-    'type': 'array',
-    'items': {'$ref': '#/definitions/interface'}
-}
-
 
 def test_router_interfaces(router, client):
     rv = client.post(
diff --git a/test/test_classifier_routes.py b/test/test_classifier_routes.py
index 8b76c1b5368d1027b1623524ab309bfb1be78f24..83b358bf0fd8911e041612c45641b11cd3853538 100644
--- a/test/test_classifier_routes.py
+++ b/test/test_classifier_routes.py
@@ -3,191 +3,14 @@ import json
 import jsonschema
 import pytest
 
+from inventory_provider.routes.classifier_schema \
+    import JUNIPER_LINK_RESPONSE_SCHEMA, PEER_INFO_RESPONSE_SCHEMA
+
 DEFAULT_REQUEST_HEADERS = {
     "Content-type": "application/json",
     "Accept": ["application/json"]
 }
 
-LOCATIONS_DEFINITIONS = {
-    "location-endpoint": {
-        "type": "object",
-        "properties": {
-            "equipment": {"type": "string"},
-            "name": {"type": "string"},
-            "abbreviation": {"type": "string"}
-        },
-        "required": ["equipment", "name", "abbreviation"],
-        "additionalProperties": False
-    },
-    "location": {
-        "type": "object",
-        "properties": {
-            "a": {"$ref": "#/definitions/location-endpoint"},
-            "b": {"$ref": "#/definitions/location-endpoint"}
-        },
-        "required": ["a"],
-        "additionalProperties": False
-    },
-    "locations-list": {
-        "type": "array",
-        "items": {"$ref": "#/definitions/location"}
-    }
-}
-
-JUNIPER_LINK_METADATA_DEFINITIONS = {
-    "ip-address": {
-        "type": "string",
-        "oneOf": [
-            {"pattern": r'^(\d+\.){3}\d+$'},
-            {"pattern": r'^([a-f\d]{4}:){7}[a-f\d]{4}$'}
-        ]
-    },
-    "ipv4-interface-address": {
-        "type": "string",
-        "pattern": r'^(\d+\.){3}\d+/\d+$'
-    },
-    "ipv6-interface-address": {
-        "type": "string",
-        "pattern": r'^[a-f\d:]+/\d+$'
-    },
-    "snmp-info": {
-        "type": "object",
-        "properties": {
-            "community": {"type": "string"},
-            "index": {"type": "integer"}
-        },
-        "required": ["community", "index"],
-        "additionalProperties": False
-    },
-    "interface-info": {
-        "type": "object",
-        "properties": {
-            "name": {"type": "string"},
-            "description": {"type": "string"},
-            "ipv4": {
-                "type": "array",
-                "items": {"$ref": "#/definitions/ipv4-interface-address"}
-            },
-            "ipv6": {
-                "type": "array",
-                "items": {"$ref": "#/definitions/ipv6-interface-address"}
-            },
-
-            # TODO: check what's changed: added to make tests pass
-            'bundle': {"type": "array"},
-            'bundle_members': {"type": "array"},
-            'snmp':  {"$ref": "#/definitions/snmp-info"}
-        },
-        "required": ["name", "description", "ipv4", "ipv6"],
-        "additionalProperties": False
-    },
-    "service-info": {
-        "type": "object",
-        "properties": {
-            "id": {"type": "integer"},
-            "name": {"type": "string"},
-            "status": {
-                "type": "string",
-                "enum": ["operational", "installed", "planned", "ordered"]
-            },
-            "circuit_type": {
-                "type": "string",
-                "enum": ["path", "service", "l2circuit"]
-            },
-            "service_type": {"type": "string"},
-            "project": {"type": "string"},
-            "equipment": {"type": "string"},
-            "pop": {"type": "string"},
-            "pop_abbreviation": {"type": "string"},
-
-            "other_end_pop": {"type": "string"},
-            "other_end_pop_abbreviation": {"type": "string"},
-            "other_end_equipment": {"type": "string"},
-            "port": {"type": "string"},
-            "other_end_port": {"type": "string"},
-            "logical_unit": {
-                "oneOf": [
-                    {"type": "integer"},
-                    {"type": "string", "maxLength": 0}
-                ]
-            },
-            "other_end_logical_unit": {
-                "oneOf": [
-                    {"type": "integer"},
-                    {"type": "string", "maxLength": 0}
-                ]
-            },
-            "manufacturer": {
-                "type": "string",
-                "enum": ["juniper", "coriant", "infinera",
-                         "cisco", "hewlett packard",
-                         "corsa", "graham smith uk ltd",
-                         "unknown", ""]
-            },
-            "card_id": {"type": "string"},
-            "other_end_card_id": {"type": "string"},
-            "interface_name": {"type": "string"},
-            "other_end_interface_name": {"type": "string"},
-
-            # TODO: check what's changed: added to make tests pass
-            'other_end_pop_name': {"type": "string"},
-            'pop_name': {"type": "string"}
-        },
-        # TODO: modify service-info so that "" entries are just omitted
-        #       (... rather than requiring 'oneOf')
-        # TODO: put 'other_end_*' params in a sub dictionary
-        # "required": [
-        #     "id", "name", "status",
-        #     "circuit_type", "service_type",
-        #     "project", "port", "manufacturer",
-        #     "equipment", "logical_unit", "card_id", "interface_name"
-        # ],
-        "additionalProperties": False
-    },
-    "related-service-info": {
-        "type": "object",
-        "properties": {
-            "name": {"type": "string"},
-            "status": {
-                "type": "string",
-                "enum": ["operational", "installed", "planned", "ordered"]
-            },
-            "circuit_type": {
-                "type": "string",
-                "enum": ["path", "service", "l2circuit"]
-            },
-            "project": {"type": "string"}
-        },
-        "required": ["name", "status", "circuit_type", "project"],
-        "additionalProperties": False
-    }
-}
-
-JUNIPER_LINK_METADATA = {
-    "$schema": "http://json-schema.org/draft-07/schema#",
-    "type": "object",
-
-    "definitions": {
-        **JUNIPER_LINK_METADATA_DEFINITIONS, **LOCATIONS_DEFINITIONS
-    },
-
-    "type": "object",
-    "properties": {
-        "services": {
-            "type": "array",
-            "items": {"$ref": "#/definitions/service-info"}
-        },
-        "interface": {"$ref": "#/definitions/interface-info"},
-        "related-services": {
-            "type": "array",
-            "items": {"$ref": "#/definitions/related-service-info"}
-        },
-        "locations": {"$ref": "#/definitions/locations-list"}
-    },
-    "required": ["interface", "locations"],
-    "additionalProperties": False
-}
-
 
 def test_juniper_link_info(client):
     rv = client.get(
@@ -196,7 +19,7 @@ def test_juniper_link_info(client):
     assert rv.status_code == 200
     assert rv.is_json
     response_data = json.loads(rv.data.decode('utf-8'))
-    jsonschema.validate(response_data, JUNIPER_LINK_METADATA)
+    jsonschema.validate(response_data, JUNIPER_LINK_RESPONSE_SCHEMA)
 
 
 def test_juniper_link_info_not_found(client):
@@ -207,7 +30,7 @@ def test_juniper_link_info_not_found(client):
     assert rv.status_code == 200
     assert rv.is_json
     response_data = json.loads(rv.data.decode('utf-8'))
-    jsonschema.validate(response_data, JUNIPER_LINK_METADATA)
+    jsonschema.validate(response_data, JUNIPER_LINK_RESPONSE_SCHEMA)
     assert response_data == {
         'interface': {
             'name': 'unknown-interface-name',
@@ -234,7 +57,7 @@ def test_juniper_link_unknown_router(client):
     assert rv.status_code == 200
     assert rv.is_json
     response_data = json.loads(rv.data.decode('utf-8'))
-    jsonschema.validate(response_data, JUNIPER_LINK_METADATA)
+    jsonschema.validate(response_data, JUNIPER_LINK_RESPONSE_SCHEMA)
     assert response_data == {
         'interface': {
             'name': 'unknown-interface-name',
@@ -266,150 +89,6 @@ IX_PUBLIC_PEER_INFO_KEYS = {
 ])
 def test_peer_info(
         client, peer_address, expected_response_keys):
-    response_schema_definitions = {
-        "ip-address": {
-            "type": "string",
-            "oneOf": [
-                {"pattern": r'^(\d+\.){3}\d+$'},
-                {"pattern": r'^([a-f\d]{4}:){7}[a-f\d]{4}$'}
-            ]
-        },
-        "interface-address": {
-            "type": "string",
-            "oneOf": [
-                {"pattern": r'^(\d+\.){3}\d+/\d+$'},
-                {"pattern": r'^[a-f\d:]+/\d+$'}
-            ]
-        },
-        "vpn-rr-peer": {
-            "type": "object",
-            "properties": {
-                "name": {"$ref": "#/definitions/ip-address"},
-                "description": {"type": "string"},
-                "peer-as": {"type": "integer"},
-                "router": {"type": "string"}
-            },
-            "required": ["name", "description"],
-            "additionalProperties": False
-        },
-        "ix-public-peer": {
-            "type": "object",
-            "properties": {
-                "name": {"$ref": "#/definitions/ip-address"},
-                "description": {"type": "string"},
-                "router": {"type": "string"},
-                "as": {
-                    "type": "object",
-                    "properties": {
-                        "local": {"type": "integer"},
-                        "peer": {"type": "integer"},
-                    },
-                    "required": ["local", "peer"],
-                    "additionalProperties": False
-                }
-            },
-            "required": ["name", "description", "as"],
-            "additionalProperties": False
-        },
-        "ix-public-peer-list": {
-            "type": "array",
-            "items": {"$ref": "#/definitions/ip-address"}
-        },
-        "ix-public-peer-info": {
-            "type": "object",
-            "properties": {
-                "peer": {"$ref": "#/definitions/ix-public-peer"},
-                "group": {"$ref": "#/definitions/ix-public-peer-list"},
-                "router": {"$ref": "#/definitions/ix-public-peer-list"}
-            },
-            "required": ["peer", "group", "router"],
-            "additionalProperties": False
-        },
-        "interface-info": {
-            "type": "object",
-            "properties": {
-                "name": {"$ref": "#/definitions/ip-address"},
-                "interface address": {
-                    "$ref": "#/definitions/interface-address"},
-                "interface name": {"type": "string"},
-                "router": {"type": "string"}
-            },
-            "required": [
-                "name", "interface address", "interface name", "router"],
-            "additionalProperties": False
-        },
-        "service-info": {
-            "type": "object"
-        },
-        "interface-lookup-info": {
-            "type": "object",
-            "properties": {
-                "interface": {"$ref": "#/definitions/interface-info"},
-                "services": {
-                    "type": "array",
-                    "items": {"$ref": "#/definitions/service-info"}
-                }
-            }
-        },
-        "snmp-info": {
-            "type": "object",
-            "properties": {
-                "hostname": {"type": "string"},
-                "oid": {"type": "string"},
-                "community": {"type": "string"}
-            },
-            "required": ["oid", "community", "hostname"],
-            "additionalProperties": False
-        },
-        "asn-group-member": {
-            "type": "object",
-            "properties": {
-                "router": {"type": "string"},
-                "address": {"type": "string"},
-                "group": {"type": "string"}
-            },
-            "required": ["router", "address", "group"],
-            "additionalProperties": False
-        },
-        "asn-group": {
-            "type": "object",
-            "properties": {
-                "asn": {"type": "integer"},
-                "peers": {
-                    "type": "array",
-                    "items": {"$ref": "#/definitions/asn-group-member"}
-                }
-            },
-            "required": ["asn", "peers"],
-            "additionalProperties": False
-        }
-    }
-    response_schema = {
-        "$schema": "http://json-schema.org/draft-07/schema#",
-        "type": "object",
-
-        "definitions": {
-            **response_schema_definitions, **LOCATIONS_DEFINITIONS
-        },
-
-        "type": "object",
-        "properties": {
-            "ix-public-peer-info": {
-                "$ref": "#/definitions/ix-public-peer-info"},
-            "vpn-rr-peer-info": {"$ref": "#/definitions/vpn-rr-peer"},
-            "interfaces": {
-                "type": "array",
-                "items": {"$ref": "#/definitions/interface-lookup-info"}
-            },
-            "locations": {"$ref": "#/definitions/locations-list"},
-            "snmp": {
-                "type": "array",
-                "items": {"$ref": "#/definitions/snmp-info"}
-            },
-            "asn": {"$ref": "#/definitions/asn-group"}
-        },
-        "additionalProperties": False
-    }
 
     rv = client.get(
         '/classifier/peer-info/%s' % peer_address,
@@ -417,7 +96,7 @@ def test_peer_info(
     assert rv.status_code == 200
     assert rv.is_json
     response_data = json.loads(rv.data.decode('utf-8'))
-    jsonschema.validate(response_data, response_schema)
+    jsonschema.validate(response_data, PEER_INFO_RESPONSE_SCHEMA)
 
     assert set(response_data.keys()) == expected_response_keys
 
@@ -448,6 +127,8 @@ def test_coriant_info(
     """
     just check that entity_name is correctly parsed and the correct
     method is called, but mock out all sql access
+
+    # TODO: schema validation
     """
     CONNECTION = 'bogus connection'
 
diff --git a/test/test_general_poller_routes.py b/test/test_general_poller_routes.py
index 5241978808dacd44b87e31b778c2889ededc9720..f3974eec19594c51da31a76fbece6b279e79959b 100644
--- a/test/test_general_poller_routes.py
+++ b/test/test_general_poller_routes.py
@@ -1,61 +1,12 @@
 import json
 import jsonschema
+from inventory_provider.routes.poller import INTERFACE_LIST_SCHEMA
 
 DEFAULT_REQUEST_HEADERS = {
     "Content-type": "application/json",
     "Accept": ["application/json"]
 }
 
-INTERFACE_LIST_SCHEMA = {
-    '$schema': 'http://json-schema.org/draft-07/schema#',
-
-    'definitions': {
-        'service': {
-            'type': 'object',
-            'properties': {
-                'id': {'type': 'integer'},
-                'name': {'type': 'string'},
-                'type': {'type': 'string'},
-                'status': {'type': 'string'},
-            },
-            'required': ['id', 'name', 'type', 'status'],
-            'additionalProperties': False
-        },
-        'interface': {
-            'type': 'object',
-            'properties': {
-                'router': {'type': 'string'},
-                'name': {'type': 'string'},
-                'description': {'type': 'string'},
-                'snmp-index': {
-                    'type': 'integer',
-                    'minimum': 1
-                },
-                'bundle': {
-                    'type': 'array',
-                    'items': {'type': 'string'}
-                },
-                'bundle-parents': {
-                    'type': 'array',
-                    'items': {'type': 'string'}
-                },
-                'circuits': {
-                    'type': 'array',
-                    'items': {'$ref': '#/definitions/service'}
-                }
-            },
-            'required': [
-                'router', 'name', 'description',
-                'snmp-index', 'bundle', 'bundle-parents',
-                'circuits'],
-            'additionalProperties': False
-        },
-    },
-
-    'type': 'array',
-    'items': {'$ref': '#/definitions/interface'}
-}
-
 
 def test_get_all_interfaces(client):
     rv = client.get(
diff --git a/test/test_general_routes.py b/test/test_general_routes.py
index 116e89f412329b2d053bcafe6531f55e12398e40..228a5db470bc2d3a5607d8410b4e706bed31d1be 100644
--- a/test/test_general_routes.py
+++ b/test/test_general_routes.py
@@ -2,6 +2,7 @@ import json
 import jsonschema
 
 from inventory_provider.routes import common
+from inventory_provider.routes.default import VERSION_SCHEMA
 
 DEFAULT_REQUEST_HEADERS = {
     "Content-type": "application/json",
@@ -11,47 +12,13 @@ DEFAULT_REQUEST_HEADERS = {
 
 def test_version_request(client, mocked_redis):
 
-    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"},
-                },
-                "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
-    }
-
     rv = client.post(
         "version",
         headers=DEFAULT_REQUEST_HEADERS)
     assert rv.status_code == 200
     jsonschema.validate(
         json.loads(rv.data.decode("utf-8")),
-        version_schema)
+        VERSION_SCHEMA)
 
 
 def test_load_json_docs(data_config, mocked_redis):
diff --git a/test/test_infinera_classifier.py b/test/test_infinera_classifier.py
index 6e0621a08d20abdc737f95415bbbbca1cf8966b8..c4d2488fc41e91d9b2f782b048da16f0b2547553 100644
--- a/test/test_infinera_classifier.py
+++ b/test/test_infinera_classifier.py
@@ -1,145 +1,13 @@
 import json
 import jsonschema
+from inventory_provider.routes.classifier_schema \
+    import INFINERA_LAMBDA_INFO_RESPONSE_SCHEMA
 
 DEFAULT_REQUEST_HEADERS = {
     "Content-type": "application/json",
     "Accept": ["application/json"]
 }
 
-LOCATIONS_DEFINITIONS = {
-    "location-endpoint": {
-        "type": "object",
-        "properties": {
-            "equipment": {"type": "string"},
-            "name": {"type": "string"},
-            "abbreviation": {"type": "string"}
-        },
-        "required": ["equipment", "name", "abbreviation"],
-        "additionalProperties": False
-    },
-    "location": {
-        "type": "object",
-        "properties": {
-            "a": {"$ref": "#/definitions/location-endpoint"},
-            "b": {"$ref": "#/definitions/location-endpoint"}
-        },
-        "required": ["a"],
-        "additionalProperties": False
-    },
-    "locations-list": {
-        "type": "array",
-        "items": {"$ref": "#/definitions/location"}
-    }
-}
-
-INFINERA_LINK_METADATA_DEFINITIONS = {
-    "service-info": {
-        "type": "object",
-        "properties": {
-            "id": {"type": "integer"},
-            "name": {"type": "string"},
-            "status": {
-                "type": "string",
-                "enum": ["operational", "installed", "planned", "ordered"]
-            },
-            "circuit_type": {
-                "type": "string",
-                "enum": ["path", "service", "l2circuit"]
-            },
-            "service_type": {"type": "string"},
-            "project": {"type": "string"},
-            "pop_name": {"type": "string"},
-            "pop_abbreviation": {"type": "string"},
-            "other_end_pop_name": {"type": "string"},
-            "other_end_pop_abbreviation": {"type": "string"},
-            "equipment": {"type": "string"},
-            "other_end_equipment": {"type": "string"},
-            "port": {"type": "string"},
-            "other_end_port": {"type": "string"},
-            "logical_unit": {
-                "oneOf": [
-                    {"type": "integer"},
-                    {"type": "string", "maxLength": 0}
-                ]
-            },
-            "other_end_logical_unit": {
-                "oneOf": [
-                    {"type": "integer"},
-                    {"type": "string", "maxLength": 0}
-                ]
-            },
-            "manufacturer": {
-                "type": "string",
-                "enum": ["juniper", "coriant", "infinera",
-                         "cisco", "hewlett packard",
-                         "corsa", "graham smith uk ltd",
-                         "unknown", ""]
-            },
-            "card_id": {"type": "string"},
-            "other_end_card_id": {"type": "string"},
-            "interface_name": {"type": "string"},
-            "other_end_interface_name": {"type": "string"}
-        },
-        "additionalProperties": False
-    },
-    "related-service-info": {
-        "type": "object",
-        "properties": {
-            "name": {"type": "string"},
-            "status": {
-                "type": "string",
-                "enum": ["operational", "installed", "planned", "ordered"]
-            },
-            "circuit_type": {
-                "type": "string",
-                "enum": ["path", "service", "l2circuit"]
-            },
-            "project": {"type": "string"}
-        },
-        "required": ["name", "status", "circuit_type", "project"],
-        "additionalProperties": False
-    },
-    "geant-lambda": {
-        "type": "object",
-        "properties": {
-            "id": {"type": "integer"},
-            "name": {"type": "string"},
-            "project": {"type": "string"},
-            "status": {
-                "type": "string",
-                "enum": ["operational", "installed", "planned", "ordered"]
-            },
-        },
-        "additionalProperties": False
-    }
-}
-
-INFINERA_LINK_METADATA = {
-    "$schema": "http://json-schema.org/draft-07/schema#",
-    "type": "object",
-
-    "definitions": {
-        **INFINERA_LINK_METADATA_DEFINITIONS, **LOCATIONS_DEFINITIONS
-    },
-
-    "type": "object",
-    "properties": {
-        "services": {
-            "type": "array",
-            "items": {"$ref": "#/definitions/service-info"}
-        },
-        "related-services": {
-            "type": "array",
-            "items": {"$ref": "#/definitions/related-service-info"}
-        },
-        "geant-lambda": {
-            "$ref": "#/definitions/geant-lambda"
-        },
-        "locations": {"$ref": "#/definitions/locations-list"}
-    },
-    "additionalProperties": False
-}
-
 
 def test_trap_metadata(client):
     rv = client.get(
@@ -149,4 +17,4 @@ def test_trap_metadata(client):
     assert rv.status_code == 200
     assert rv.is_json
     response_data = json.loads(rv.data.decode('utf-8'))
-    jsonschema.validate(response_data, INFINERA_LINK_METADATA)
+    jsonschema.validate(response_data, INFINERA_LAMBDA_INFO_RESPONSE_SCHEMA)
diff --git a/test/test_job_routes.py b/test/test_job_routes.py
index 33bbde510defb4d7ca6642aa77a1292e2531c711..f3a4df17d8541322bda4af2634d91ab59e90bb23 100644
--- a/test/test_job_routes.py
+++ b/test/test_job_routes.py
@@ -2,59 +2,15 @@ import json
 import jsonschema
 
 from inventory_provider.tasks.common import _get_redis, DB_LATCH_SCHEMA
+from inventory_provider.routes.jobs \
+    import INDIVIDUAL_TASK_STATUS_RESPONSE_SCHEMA, \
+    TASK_LOG_RESPONSE_SCHEMA, TASK_ID_RESPONSE_SCHEMA
 
 DEFAULT_REQUEST_HEADERS = {
     "Content-type": "application/json",
     "Accept": ["application/json"]
 }
 
-TASK_ID_RESPONSE_SCHEMA = {
-    "$schema": "http://json-schema.org/draft-07/schema#",
-    "type": "object",
-    "properties": {
-        "task id": {"type": "string"}
-    },
-    "required": ["task id"],
-    "additionalProperties": False
-}
-
-TASK_STATUS_SCHEMA = {
-    "$schema": "http://json-schema.org/draft-07/schema#",
-    "definitions": {
-        "task": {
-            "type": "object",
-            "properties": {
-                "id": {"type": "string"},
-                "status": {"type": "string"},
-                "exception": {"type": "boolean"},
-                "ready": {"type": "boolean"},
-                "success": {"type": "boolean"},
-                "result": {"type": ["object", "null"]},
-                "parent": {"type": ["string", "null"]}
-            },
-            "required": [
-                "id", "status", "exception", "ready", "success", "parent"],
-            "additionalProperties": False
-        }
-    },
-
-    "type": "array",
-    "items": {"$ref": "#/definitions/task"}
-}
-
-TASK_LOG_SCHEMA = {
-    "$schema": "http://json-schema.org/draft-07/schema#",
-    "type": "object",
-    "properties": {
-        "pending": {"type": "array", "items": {"type": "string"}},
-        "errors": {"type": "array", "items": {"type": "string"}},
-        "failed": {"type": "array", "items": {"type": "string"}},
-        "warnings": {"type": "array", "items": {"type": "string"}},
-    },
-    "required": ["pending", "errors", "failed", "warnings"],
-    "additionalProperties": False
-}
-
 
 def backend_db():
     return _get_redis({
@@ -175,7 +131,7 @@ def test_check_update_status(client, mocker):
         headers=DEFAULT_REQUEST_HEADERS)
     assert rv.status_code == 200
     result = json.loads(rv.data.decode('utf-8'))
-    jsonschema.validate(result, TASK_STATUS_SCHEMA)
+    jsonschema.validate(result, INDIVIDUAL_TASK_STATUS_RESPONSE_SCHEMA)
     for status in result:
         assert status['id'] == 'zz55'
         assert status['status'] == 'SUCCESS'
@@ -207,7 +163,7 @@ def test_check_task_status_success(client, mocker):
         headers=DEFAULT_REQUEST_HEADERS)
     assert rv.status_code == 200
     result = json.loads(rv.data.decode('utf-8'))
-    jsonschema.validate(result, TASK_STATUS_SCHEMA)
+    jsonschema.validate(result, INDIVIDUAL_TASK_STATUS_RESPONSE_SCHEMA)
     for status in result:
         assert status['id'] == 'abc'
         assert status['status'] == 'SUCCESS'
@@ -228,7 +184,7 @@ def test_check_task_status_custom_status(client, mocker):
         headers=DEFAULT_REQUEST_HEADERS)
     assert rv.status_code == 200
     result = json.loads(rv.data.decode('utf-8'))
-    jsonschema.validate(result, TASK_STATUS_SCHEMA)
+    jsonschema.validate(result, INDIVIDUAL_TASK_STATUS_RESPONSE_SCHEMA)
     for status in result:
         assert status['id'] == 'xyz'
         assert status['status'] == 'custom'
@@ -248,7 +204,7 @@ def test_check_task_status_exception(client, mocker):
         headers=DEFAULT_REQUEST_HEADERS)
     assert rv.status_code == 200
     result = json.loads(rv.data.decode('utf-8'))
-    jsonschema.validate(result, TASK_STATUS_SCHEMA)
+    jsonschema.validate(result, INDIVIDUAL_TASK_STATUS_RESPONSE_SCHEMA)
     for status in result:
         assert status['id'] == '123-xyz.ABC'
         assert status['status'] == 'FAILURE'
@@ -380,7 +336,7 @@ def test_job_log(client):
         headers=DEFAULT_REQUEST_HEADERS)
     assert rv.status_code == 200
     result = json.loads(rv.data.decode('utf-8'))
-    jsonschema.validate(result, TASK_LOG_SCHEMA)
+    jsonschema.validate(result, TASK_LOG_RESPONSE_SCHEMA)
 
     assert len(result['errors']) == 3
     assert len(result['pending']) == 3
diff --git a/test/test_lg_routes.py b/test/test_lg_routes.py
index e45e3b599bc0d4a2bc293e78ba95e01af9fb91e8..add33c4b9b1008931fc90fd1d6e0683dd36b3cb4 100644
--- a/test/test_lg_routes.py
+++ b/test/test_lg_routes.py
@@ -1,57 +1,13 @@
 import json
 import jsonschema
 import pytest
+from inventory_provider.routes.lg import LG_ROUTERS_SCHEMA
 
 DEFAULT_REQUEST_HEADERS = {
     "Content-type": "application/json",
     "Accept": ["application/json"]
 }
 
-LG_ROUTERS_SCHEMA = {
-    "$schema": "http://json-schema.org/draft-07/schema#",
-
-    "definitions": {
-        "pop-info": {
-            "type": "object",
-            "properties": {
-                "name": {"type": "string"},
-                "abbreviation": {"type": "string"},
-                "country": {"type": "string"},
-                "country code": {"type": "string"},
-                "city": {"type": "string"},
-                "longitude": {"type": "number"},
-                "latitude": {"type": "number"}
-            },
-            "required": [
-                "name",
-                "abbreviation",
-                "country",
-                "country code",
-                "city",
-                "longitude",
-                "latitude"
-            ],
-            "additionalProperties": False
-        },
-        "router": {
-            "type": "object",
-            "properties": {
-                "equipment name": {"type": "string"},
-                "type": {
-                    "type": "string",
-                    "enum": ["INTERNAL", "CORE"]
-                },
-                "pop": {"$ref": "#/definitions/pop-info"}
-            },
-            "required": ["equipment name", "type", "pop"],
-            "additionalProperties": False
-        }
-    },
-
-    "type": "array",
-    "items": {"$ref": "#/definitions/router"}
-}
-
 
 def test_public_routers(client):
     rv = client.get(
diff --git a/test/test_msr_routes.py b/test/test_msr_routes.py
index 47a9d42e304b2d270c5ea60b6d7a2df09d3a13d8..b842938d24d6e89679f58c853cacf03d92f22624 100644
--- a/test/test_msr_routes.py
+++ b/test/test_msr_routes.py
@@ -1,41 +1,12 @@
 import json
 import jsonschema
+from inventory_provider.routes.msr import ACCESS_SERVICES_LIST_SCHEMA
 
 DEFAULT_REQUEST_HEADERS = {
     "Content-type": "application/json",
     "Accept": ["application/json"]
 }
 
-ACCESS_SERVICES_LIST_SCHEMA = {
-    "$schema": "http://json-schema.org/draft-07/schema#",
-
-    "definitions": {
-        "service": {
-            "type": "object",
-            "properties": {
-                "id": {"type": "integer"},
-                "name": {"type": "string"},
-                "equipment": {"type": "string"},
-                "pop_name": {"type": "string"},
-                "other_end_equipment": {"type": "string"},
-                "other_end_pop_name": {"type": "string"},
-                "speed_value": {"type": "integer"},
-                "speed_unit": {"type": "string"}
-            },
-            "required": [
-                "id", "name",
-                "pop_name", "equipment",
-                "other_end_pop_name", "other_end_equipment",
-                "speed_value", "speed_unit"
-            ],
-            "additionalProperties": False
-        }
-    },
-
-    "type": "array",
-    "items": {"$ref": "#/definitions/service"}
-}
-
 
 def test_access_services(client):
     rv = client.get(
diff --git a/tox.ini b/tox.ini
index f08e8f1c3512dd36edca87ebb6b20de3a3212053..8d569cbc25d09e7397d4dc13d97de759cdcbf228 100644
--- a/tox.ini
+++ b/tox.ini
@@ -17,4 +17,5 @@ commands =
     coverage report --fail-under 75
     # coverage report --fail-under 80
     flake8
+    sphinx-build -M html docs/source docs/build