diff --git a/docs/requirements.txt b/docs/requirements.txt
index cd7c3062a54c674c19c4d182d65b5a2882606fcf..cc674ad653b6692568a1cc20b2cc37d986df896a 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,2 +1,2 @@
 sphinx
-sphinxcontrib-openapi
+sphinxcontrib-redoc
diff --git a/docs/source/conf.py b/docs/source/conf.py
index f5dd3d43d2312c65969dc841ef4da5f05135107f..2d89320ea72464347ae633ad812dd074f43f4139 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -39,7 +39,17 @@ release = '0.0'
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
 extensions = [
-    "sphinxcontrib.openapi",
+    "sphinxcontrib.redoc",
+]
+
+# Configure redoc
+redoc = [
+    {
+        'name': 'ZKAPAuthorizer Backup/Recovery API',
+        'page': 'designs/backup-recovery-openapi',
+        'spec': '../../src/_zkapauthorizer/backup-recovery.yaml',
+        'embed': True,
+    },
 ]
 
 # Add any paths that contain templates here, relative to this directory.
@@ -86,7 +96,7 @@ 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']
+html_static_path = []
 
 # Custom sidebar templates, must be a dictionary that maps document names
 # to template names.
diff --git a/docs/source/designs/backup-recovery.rst b/docs/source/designs/backup-recovery.rst
index c8613779e927873760f0e19ee76546065d355b76..51eb61753b2bf5739cc6e4477a28910eca14db23 100644
--- a/docs/source/designs/backup-recovery.rst
+++ b/docs/source/designs/backup-recovery.rst
@@ -205,7 +205,7 @@ Backup operations resume as usual from this point using the existing on-grid sta
 External Interfaces
 ~~~~~~~~~~~~~~~~~~~
 
-.. openapi:: ./backup-recovery.yaml
+See the `OpenAPI specification <backup-recovery-openapi.html>`_.
 
 Data Integrity
 ~~~~~~~~~~~~~~
diff --git a/requirements/test.in b/requirements/test.in
index 93cf1deaefec1eb0aaa179f111d4533fa3c8b8b7..21bf9712cd65c8471008b30a24736d51084f1e5a 100644
--- a/requirements/test.in
+++ b/requirements/test.in
@@ -3,3 +3,4 @@ fixtures
 testtools
 hypothesis
 pyflakes
+openapi_spec_validator
diff --git a/setup.cfg b/setup.cfg
index 99a57fb165ca22ac3f1f97e160667f09e890bbe4..9c731ef8b8531382726e2a33d5c323bcbfc0a97a 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -58,7 +58,7 @@ install_requires =
     colorama
 
 [options.extras_require]
-test = coverage; fixtures; testtools; hypothesis
+test = coverage; fixtures; testtools; hypothesis; openapi_spec_validator
 
 [flake8]
 # Enforce all pyflakes constraints, and also prohibit tabs for indentation.
diff --git a/docs/source/designs/backup-recovery.yaml b/src/_zkapauthorizer/backup-recovery.yaml
similarity index 88%
rename from docs/source/designs/backup-recovery.yaml
rename to src/_zkapauthorizer/backup-recovery.yaml
index f51b631c1dceba424e4005a2e6182407dad464e2..45eb8700be5a259663a7bc3d702d9c8a4ee4095d 100644
--- a/docs/source/designs/backup-recovery.yaml
+++ b/src/_zkapauthorizer/backup-recovery.yaml
@@ -51,6 +51,19 @@ paths:
                 properties: {}
 
   /storage-plugins/privatestorageio-zkapauthz-v1/backup:
+    get:
+      description: >-
+        Retrieve information about the backup configuration and state of this
+        node.
+      responses:
+        200:
+          description: >-
+            Information about backup configuration is available and included
+            in the response.
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/BackupConfiguration"
     post:
       description: |
         Configure ZKAPAuthorizer to maintain an on-grid backup of its state or
@@ -96,6 +109,8 @@ components:
             is the capability which can be submitted in order to initiate a
             recovery from the backup.
 
+
+
   responses:
     ErrorResponse:
       description: >-
diff --git a/src/_zkapauthorizer/tests/test_client_resource.py b/src/_zkapauthorizer/tests/test_client_resource.py
index 130294e54becd81e70c206c3dcbefd96f9303d0d..3ada6f6aef6e88eaa8a016779a8af43841e6136b 100644
--- a/src/_zkapauthorizer/tests/test_client_resource.py
+++ b/src/_zkapauthorizer/tests/test_client_resource.py
@@ -43,6 +43,8 @@ from hypothesis.strategies import (
     text,
     tuples,
 )
+from openapi_spec_validator import validate_spec
+from openapi_spec_validator.readers import read_from_filename
 from testtools import TestCase
 from testtools.content import text_content
 from testtools.matchers import (
@@ -66,6 +68,7 @@ from twisted.web.http import BAD_REQUEST, NOT_FOUND, NOT_IMPLEMENTED, OK, UNAUTH
 from twisted.web.http_headers import Headers
 from twisted.web.resource import IResource, getChildForRequest
 
+from .. import __file__ as package_init_file
 from .. import __version__ as zkapauthorizer_version
 from .._base64 import urlsafe_b64decode
 from .._json import dumps_utf8
@@ -285,6 +288,20 @@ def add_api_token_to_config(basedir, config, api_auth_token):
     config.write_private_config("api_auth_token", api_auth_token)
 
 
+class OpenAPITests(TestCase):
+    """
+    Tests for the OpenAPI specification for the HTTP API.
+    """
+    def test_backup_recovery_valid(self):
+        """
+        The specification document is valid OpenAPI 3.0.
+        """
+        spec_path = FilePath(package_init_file).sibling("backup-recovery.yaml")
+        spec_dict, spec_url = read_from_filename(spec_path.path)
+        # If no exception is raised then the spec is valid.
+        validate_spec(spec_dict)
+
+
 class FromConfigurationTests(TestCase):
     """
     Tests for ``from_configuration``.