diff --git a/config/routing.py b/config/routing.py
new file mode 100644
index 0000000000000000000000000000000000000000..1175710ebfcf9b462b60b2660c450cdc5348b9e3
--- /dev/null
+++ b/config/routing.py
@@ -0,0 +1,18 @@
+from django.conf.urls import url
+
+from channels.routing import ProtocolTypeRouter, URLRouter
+
+from retribute_api.search import consumers
+
+application = ProtocolTypeRouter(
+    {
+        "http": URLRouter(
+            [
+                url(
+                    r"^api/v1/search/(?P<lookup_type>.+):(?P<lookup>.+)$",
+                    consumers.SearchSingleConsumer,
+                )
+            ]
+        )
+    }
+)
diff --git a/retribute_api/search/consumers.py b/retribute_api/search/consumers.py
new file mode 100644
index 0000000000000000000000000000000000000000..2cc4e2af08917306a03a8f165593a8eb3a2506fc
--- /dev/null
+++ b/retribute_api/search/consumers.py
@@ -0,0 +1,35 @@
+import aiohttp.client
+import json
+
+from channels.generic.http import AsyncHttpConsumer
+
+from . import sources
+
+
+async def json_response(self, status, content):
+    await self.send_response(
+        status,
+        json.dumps(content, indent=2, sort_keys=True).encode("utf-8"),
+        headers=[{"Content-Type": "application/json"}],
+    )
+
+
+class SearchSingleConsumer(AsyncHttpConsumer):
+    async def handle(self, body):
+        lookup_type = self.scope["url_route"]["kwargs"]["lookup_type"]
+        lookup = self.scope["url_route"]["kwargs"]["lookup"]
+        try:
+            source = sources.registry._data[lookup_type]
+        except KeyError:
+            await json_response(self, 400, {"detail": "Invalid lookup"})
+        try:
+            async with aiohttp.client.ClientSession() as session:
+                data = await source.get(lookup, session)
+        except Exception:
+            raise
+        try:
+            profile = sources.result_to_retribute_profile(lookup_type, lookup, data)
+        except Exception:
+            raise
+
+        await json_response(self, 200, profile)
diff --git a/retribute_api/search/sources.py b/retribute_api/search/sources.py
index 62759774532dcd2fb41f36a925a17efe181f95a9..e5a4b2328f5a2dab60a65c3efb48fead3a6519d8 100644
--- a/retribute_api/search/sources.py
+++ b/retribute_api/search/sources.py
@@ -34,11 +34,11 @@ class Activitypub(Source):
     id = "activitypub"
 
     async def get(self, lookup, session):
-        response = await session.get(
+        async with session.get(
             lookup, headers={"Accept": "application/activity+json"}
-        )
-        response.raise_for_status()
-        actor_data = await response.json()
+        ) as response:
+            response.raise_for_status()
+            actor_data = await response.json()
         serializer = activitypub.ActorSerializer(data=actor_data)
         serializer.is_valid(raise_exception=True)
         for tag in serializer.validated_data["tag"]:
@@ -64,12 +64,11 @@ class Webfinger(Source):
         found = None
         if "activitypub" in links:
             found = await Activitypub().get(links["activitypub"], session)
-
         return found
 
 
 def result_to_retribute_profile(lookup_type, lookup, data):
-    path = settings.BASE_URL + "/compat/"
+    path = settings.BASE_URL + "/compat"
     now = timezone.now()
     valid_means = [
         (link, means.extract_from_url(link["url"])) for link in data["links"]
@@ -89,7 +88,7 @@ def result_to_retribute_profile(lookup_type, lookup, data):
 
     final = {
         "version": "0.1",
-        "id": "https://retribute.me.test/compat/{}:{}".format(lookup_type, lookup),
+        "id": "{}/{}:{}".format(path, lookup_type, lookup),
         "title": "Compat profile for {}:{}".format(lookup_type, lookup),
         "updated": now.isoformat(),
         "identities": [],
diff --git a/retribute_api/search/webfinger.py b/retribute_api/search/webfinger.py
index db1777217f9987eca71246741107c4010fd82a74..84ed55f38e5bfa203875e23e1a0c29ca1165fa90 100644
--- a/retribute_api/search/webfinger.py
+++ b/retribute_api/search/webfinger.py
@@ -3,12 +3,12 @@ from rest_framework import serializers
 
 async def lookup(name, session):
     username, domain = name.split("@")
-    response = await session.get(
+    async with session.get(
         "https://{}/.well-known/webfinger".format(domain),
         params={"resource": "acct:{}".format(name)},
-    )
-    response.raise_for_status()
-    return await response.json()
+    ) as response:
+        response.raise_for_status()
+        return await response.json()
 
 
 class AccountLinkSerializer(serializers.Serializer):
diff --git a/tests/conftest.py b/tests/conftest.py
index 6f17a3a691834170b1723adbb505d437102d39ed..36e328199c73e08f579f4740ea4507cb26cff714 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -5,6 +5,7 @@ import asynctest
 
 from django.utils import timezone
 
+from config import routing
 
 pytest_plugins = "aiohttp.pytest_plugin"
 
@@ -31,3 +32,8 @@ def now(mocker):
     now = timezone.now()
     mocker.patch.object(timezone, "now", return_value=now)
     return now
+
+
+@pytest.fixture
+def application():
+    return routing.application
diff --git a/tests/search/test_consumers.py b/tests/search/test_consumers.py
new file mode 100644
index 0000000000000000000000000000000000000000..dfecbca2ce1627da2769021ec2715460d670b29c
--- /dev/null
+++ b/tests/search/test_consumers.py
@@ -0,0 +1,24 @@
+import json
+from channels.testing import HttpCommunicator
+
+from retribute_api.search import consumers
+from retribute_api.search import sources
+
+
+async def test_search_consumer_success(loop, application, mocker, coroutine_mock):
+    get = mocker.patch.object(sources.Webfinger, "get", coroutine_mock())
+    expected = {"dummy": "json"}
+    get_profile = mocker.patch.object(
+        sources, "result_to_retribute_profile", return_value=expected
+    )
+    communicator = HttpCommunicator(
+        application, "GET", "/api/v1/search/webfinger:test@user.domain"
+    )
+    response = await communicator.get_response()
+    assert get.call_args[0][0] == "test@user.domain"
+    get_profile.assert_called_once_with(
+        "webfinger", "test@user.domain", get.return_value
+    )
+    assert response["status"] == 200
+    assert response["headers"] == [{"Content-Type": "application/json"}]
+    assert response["body"] == json.dumps(expected, indent=2, sort_keys=True).encode()