diff --git a/config/routing.py b/config/routing.py
index baaa3d140c74d934a6e62a9d96c86974d057d3a9..01857480390605f129fb038e7e06c30a903e4d96 100644
--- a/config/routing.py
+++ b/config/routing.py
@@ -18,6 +18,7 @@ application = ProtocolTypeRouter(
     {
         "http": URLRouter(
             [
+                url(r"^v1/sources$", consumers.SourcesConsumer),
                 url(r"^v1/providers$", consumers.ProvidersConsumer),
                 url(r"^v1/search/$", consumers.SearchMultipleConsumer),
                 url(
diff --git a/retribute_api/search/consumers.py b/retribute_api/search/consumers.py
index c293bc30a0835e8100b7d25d842663c8275ed9f7..9b18f37209d4261f6040d3f6b683966de88c1f6a 100644
--- a/retribute_api/search/consumers.py
+++ b/retribute_api/search/consumers.py
@@ -195,3 +195,13 @@ class ProvidersConsumer(AsyncHttpConsumer):
             for _, p in sorted(providers.registry, key=lambda v: v[0])
         ]
         await json_response(self, 200, data)
+
+
+class SourcesConsumer(AsyncHttpConsumer):
+    @wrapper_500
+    async def handle(self, body):
+        data = [
+            {"id": p.id, "label": p.label, "example": p.example}
+            for p in sorted(sources.registry._data.values(), key=lambda v: v.id)
+        ]
+        await json_response(self, 200, data)
diff --git a/retribute_api/search/sources.py b/retribute_api/search/sources.py
index c7b6724a43e5f896069e1c751b694afc3dd1fa27..aea4fd3cd250d7e0543feae972c703e3f7b9eb3b 100644
--- a/retribute_api/search/sources.py
+++ b/retribute_api/search/sources.py
@@ -26,6 +26,8 @@ registry = Registry()
 
 class Source(object):
     id = None
+    label = None
+    example = None
 
     async def get(self, lookup):
         raise NotImplementedError()
@@ -34,6 +36,8 @@ class Source(object):
 @registry.register
 class Activitypub(Source):
     id = "activitypub"
+    label = "ActivityPub URL"
+    example = "https://mastodon.domain/@username"
     excluded_tags = [
         "#noretribute",
         # '#nobot',
@@ -73,6 +77,8 @@ class Activitypub(Source):
 @registry.register
 class Webfinger(Source):
     id = "webfinger"
+    label = "Fediverse/Webfinger username"
+    example = "username@mastodon.domain"
 
     async def get(self, lookup, session, cache):
         webfinger_data = await webfinger.lookup(lookup, session, cache=cache)
@@ -86,6 +92,8 @@ class Webfinger(Source):
 @registry.register
 class MusicBrainz(Source):
     id = "musicbrainz"
+    label = "MusicBrainz Artist ID"
+    example = "a2e55cf5-ca3a-4c26-ba62-fc4a4f2bc603"
 
     async def get(self, lookup, session, cache):
 
diff --git a/tests/search/test_consumers.py b/tests/search/test_consumers.py
index 4c7045f8580f82b1e674f5d7d3ce685c4b846df3..1ecba9face816c16d014601149c682f0905ac35f 100644
--- a/tests/search/test_consumers.py
+++ b/tests/search/test_consumers.py
@@ -142,3 +142,18 @@ async def test_providers(loop, application, mocker, coroutine_mock):
         (b"Access-Control-Allow-Origin", b"*"),
     ]
     assert response["body"] == json.dumps(expected, indent=2, sort_keys=True).encode()
+
+
+async def test_sources(loop, application, mocker, coroutine_mock):
+    expected = [
+        {"id": p.id, "label": p.label, "example": p.example}
+        for p in sorted(sources.registry._data.values(), key=lambda v: v.id)
+    ]
+    communicator = HttpCommunicator(application, "GET", "/v1/sources")
+    response = await communicator.get_response()
+    assert response["status"] == 200
+    assert response["headers"] == [
+        (b"Content-Type", b"application/json"),
+        (b"Access-Control-Allow-Origin", b"*"),
+    ]
+    assert response["body"] == json.dumps(expected, indent=2, sort_keys=True).encode()