diff --git a/swh/scheduler/api/server.py b/swh/scheduler/api/server.py
index b8cba45079874b85d482cf92ada83bb550e986b1..bd48a93205a37ec7a0b159333d3ab6136ed21a3a 100644
--- a/swh/scheduler/api/server.py
+++ b/swh/scheduler/api/server.py
@@ -33,6 +33,10 @@ def get_sched():
     return scheduler
 
 
+def has_no_empty_params(rule):
+    return len(rule.defaults or ()) >= len(rule.arguments or ())
+
+
 @app.route('/')
 @negotiate(MsgpackFormatter)
 @negotiate(JSONFormatter)
@@ -159,6 +163,21 @@ def delete_archived_tasks():
     return get_sched().delete_archived_tasks(**decode_request(request))
 
 
+@app.route("/site-map")
+@negotiate(MsgpackFormatter)
+@negotiate(JSONFormatter)
+def site_map():
+    links = []
+    sched = get_sched()
+    for rule in app.url_map.iter_rules():
+        if has_no_empty_params(rule) and hasattr(sched, rule.endpoint):
+            links.append(dict(
+                rule=rule.rule,
+                description=getattr(sched, rule.endpoint).__doc__))
+    # links is now a list of url, endpoint tuples
+    return links
+
+
 def run_from_webserver(environ, start_response,
                        config_path=DEFAULT_CONFIG_PATH):
     """Run the WSGI app from the webserver, loading the configuration."""
diff --git a/swh/scheduler/tests/test_api_client.py b/swh/scheduler/tests/test_api_client.py
index cba655158275a54a78384f900b73fb86a7c06434..5b525882218958eb2cbbc0e6381f31cfc37e9c86 100644
--- a/swh/scheduler/tests/test_api_client.py
+++ b/swh/scheduler/tests/test_api_client.py
@@ -4,6 +4,7 @@
 # See top-level LICENSE file for more information
 
 import unittest
+import requests
 
 from swh.core.tests.server_testing import ServerTestFixture
 from swh.scheduler import get_scheduler
@@ -34,3 +35,19 @@ class RemoteSchedulerTest(CommonSchedulerTest, ServerTestFixture,
         # accessible through a remote scheduler accessible on the
         # given port
         self.backend = get_scheduler('remote', {'url': self.url()})
+
+    def test_site_map(self):
+        sitemap = requests.get(self.url() + 'site-map')
+        assert sitemap.headers['Content-Type'] == 'application/json'
+        sitemap = sitemap.json()
+
+        rules = set(x['rule'] for x in sitemap)
+        # we expect at least these rules
+        expected_rules = set('/'+rule for rule in (
+            'set_status_tasks', 'create_task_type',
+            'get_task_type', 'get_task_types', 'create_tasks', 'disable_tasks',
+            'get_tasks', 'search_tasks', 'peek_ready_tasks',
+            'grab_ready_tasks', 'schedule_task_run', 'mass_schedule_task_runs',
+            'start_task_run', 'end_task_run', 'filter_task_to_archive',
+            'delete_archived_tasks'))
+        assert rules.issuperset(expected_rules), expected_rules - rules