diff --git a/api/funkwhale_api/federation/actors.py b/api/funkwhale_api/federation/actors.py
new file mode 100644
index 0000000000000000000000000000000000000000..5560520c20f2fc7d0c4fdf3eee7ad367e06c94e0
--- /dev/null
+++ b/api/funkwhale_api/federation/actors.py
@@ -0,0 +1,48 @@
+import requests
+
+from django.urls import reverse
+from django.conf import settings
+
+from dynamic_preferences.registries import global_preferences_registry
+
+from . import models
+
+
+def get_actor_data(actor_url):
+    response = requests.get(actor_url)
+    response.raise_for_status()
+    return response.json()
+
+
+SYSTEM_ACTORS = {
+    'library': {
+        'get_actor': lambda: models.Actor(**get_base_system_actor_arguments('library')),
+    }
+}
+
+
+def get_base_system_actor_arguments(name):
+    preferences = global_preferences_registry.manager()
+    return {
+        'preferred_username': name,
+        'domain': settings.FEDERATION_HOSTNAME,
+        'type': 'Person',
+        'name': '{}\'s library'.format(settings.FEDERATION_HOSTNAME),
+        'manually_approves_followers': True,
+        'url': reverse(
+            'federation:instance-actors-detail',
+            kwargs={'actor': name}),
+        'shared_inbox_url': reverse(
+            'federation:instance-actors-inbox',
+            kwargs={'actor': name}),
+        'inbox_url': reverse(
+            'federation:instance-actors-inbox',
+            kwargs={'actor': name}),
+        'outbox_url': reverse(
+            'federation:instance-actors-outbox',
+            kwargs={'actor': name}),
+        'public_key': preferences['federation__public_key'],
+        'summary': 'Bot account to federate with {}\'s library'.format(
+            settings.FEDERATION_HOSTNAME
+        ),
+    }
diff --git a/api/funkwhale_api/federation/authentication.py b/api/funkwhale_api/federation/authentication.py
new file mode 100644
index 0000000000000000000000000000000000000000..7972e78a01195e6ba0fc5f481b09551fdf5af1c4
--- /dev/null
+++ b/api/funkwhale_api/federation/authentication.py
@@ -0,0 +1,46 @@
+import cryptography
+
+from django.contrib.auth.models import AnonymousUser
+
+from rest_framework import authentication
+from rest_framework import exceptions
+
+from . import actors
+from . import keys
+from . import serializers
+from . import signing
+
+
+class SignatureAuthentication(authentication.BaseAuthentication):
+    def authenticate(self, request):
+        try:
+            signature = request.META['headers']['Signature']
+            key_id = keys.get_key_id_from_signature_header(signature)
+        except KeyError:
+            raise exceptions.AuthenticationFailed('No signature')
+        except ValueError as e:
+            raise exceptions.AuthenticationFailed(str(e))
+
+        try:
+            actor_data = actors.get_actor_data(key_id)
+        except Exception as e:
+            raise exceptions.AuthenticationFailed(str(e))
+
+        try:
+            public_key = actor_data['publicKey']['publicKeyPem']
+        except KeyError:
+            raise exceptions.AuthenticationFailed('No public key found')
+
+        serializer = serializers.ActorSerializer(data=actor_data)
+        if not serializer.is_valid():
+            raise exceptions.AuthenticationFailed('Invalid actor payload')
+
+        try:
+            signing.verify_django(request, public_key.encode('utf-8'))
+        except cryptography.exceptions.InvalidSignature:
+            raise exceptions.AuthenticationFailed('Invalid signature')
+
+        user = AnonymousUser()
+        ac = serializer.build()
+        setattr(request, 'actor', ac)
+        return (user, None)
diff --git a/api/funkwhale_api/federation/keys.py b/api/funkwhale_api/federation/keys.py
index 432560ef7446d5973d23850cff7a2cb253c9fba7..08d4034ea347a6a17bb5d5701217d54cc1c57fa0 100644
--- a/api/funkwhale_api/federation/keys.py
+++ b/api/funkwhale_api/federation/keys.py
@@ -2,10 +2,14 @@ from cryptography.hazmat.primitives import serialization as crypto_serialization
 from cryptography.hazmat.primitives.asymmetric import rsa
 from cryptography.hazmat.backends import default_backend as crypto_default_backend
 
+import re
 import requests
+import urllib.parse
 
 from . import exceptions
 
+KEY_ID_REGEX = re.compile(r'keyId=\"(?P<id>.*)\"')
+
 
 def get_key_pair(size=2048):
     key = rsa.generate_private_key(
@@ -25,19 +29,21 @@ def get_key_pair(size=2048):
     return private_key, public_key
 
 
-def get_public_key(actor_url):
-    """
-    Given an actor_url, request it and extract publicKey data from
-    the response payload.
-    """
-    response = requests.get(actor_url)
-    response.raise_for_status()
-    payload = response.json()
+def get_key_id_from_signature_header(header_string):
+    parts = header_string.split(',')
     try:
-        return {
-            'public_key_pem': payload['publicKey']['publicKeyPem'],
-            'id': payload['publicKey']['id'],
-            'owner': payload['publicKey']['owner'],
-        }
-    except KeyError:
-        raise exceptions.MalformedPayload(str(payload))
+        raw_key_id = [p for p in parts if p.startswith('keyId="')][0]
+    except IndexError:
+        raise ValueError('Missing key id')
+
+    match = KEY_ID_REGEX.match(raw_key_id)
+    if not match:
+        raise ValueError('Invalid key id')
+
+    key_id = match.groups()[0]
+    url = urllib.parse.urlparse(key_id)
+    if not url.scheme or not url.netloc:
+        raise ValueError('Invalid url')
+    if url.scheme not in ['http', 'https']:
+        raise ValueError('Invalid shceme')
+    return key_id
diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py
index d1533b62d626d5451cc775646d0bbc9f9f0c33a9..5f5516b2d8532b478920f99c8872d8bc957ac8fb 100644
--- a/api/funkwhale_api/federation/serializers.py
+++ b/api/funkwhale_api/federation/serializers.py
@@ -1,38 +1,101 @@
+import urllib.parse
+
 from django.urls import reverse
 from django.conf import settings
 
+from rest_framework import serializers
 from dynamic_preferences.registries import global_preferences_registry
 
+from . import models
 from . import utils
 
 
-def repr_instance_actor():
-    """
-    We do not use a serializer here, since it's pretty static
-    """
-    actor_url = utils.full_url(reverse('federation:instance-actor'))
-    preferences = global_preferences_registry.manager()
-    public_key = preferences['federation__public_key']
+class ActorSerializer(serializers.ModelSerializer):
+    # left maps to activitypub fields, right to our internal models
+    id = serializers.URLField(source='url')
+    outbox = serializers.URLField(source='outbox_url')
+    inbox = serializers.URLField(source='inbox_url')
+    following = serializers.URLField(source='following_url', required=False)
+    followers = serializers.URLField(source='followers_url', required=False)
+    preferredUsername = serializers.CharField(
+        source='preferred_username', required=False)
+    publicKey = serializers.JSONField(source='public_key', required=False)
+    manuallyApprovesFollowers = serializers.NullBooleanField(
+        source='manually_approves_followers', required=False)
+
+    class Meta:
+        model = models.Actor
+        fields = [
+            'id',
+            'type',
+            'name',
+            'summary',
+            'preferredUsername',
+            'publicKey',
+            'inbox',
+            'outbox',
+            'following',
+            'followers',
+            'manuallyApprovesFollowers',
+        ]
 
-    return {
-        '@context': [
+    def to_representation(self, instance):
+        ret = super().to_representation(instance)
+        ret['@context'] = [
             'https://www.w3.org/ns/activitystreams',
             'https://w3id.org/security/v1',
             {},
-        ],
-        'id': utils.full_url(reverse('federation:instance-actor')),
-        'type': 'Person',
-        'inbox': utils.full_url(reverse('federation:instance-inbox')),
-        'outbox': utils.full_url(reverse('federation:instance-outbox')),
-        'preferredUsername': 'service',
-        'name': 'Service Bot - {}'.format(settings.FEDERATION_HOSTNAME),
-        'summary': 'Bot account for federating with {}'.format(
-            settings.FEDERATION_HOSTNAME
-        ),
-        'publicKey': {
-            'id': '{}#main-key'.format(actor_url),
-            'owner': actor_url,
-            'publicKeyPem': public_key
-        },
-
-    }
+        ]
+        if instance.public_key:
+            ret['publicKey'] = {
+                'owner': instance.url,
+                'publicKeyPem': instance.public_key,
+                'id': '{}#main-key'.format(instance.url)
+            }
+        ret['endpoints'] = {}
+        if instance.shared_inbox_url:
+            ret['endpoints']['sharedInbox'] = instance.shared_inbox_url
+        return ret
+
+    def prepare_missing_fields(self):
+        kwargs = {}
+        domain = urllib.parse.urlparse(self.validated_data['url']).netloc
+        kwargs['domain'] = domain
+        for endpoint, url in self.initial_data.get('endpoints', {}).items():
+            if endpoint == 'sharedInbox':
+                kwargs['shared_inbox_url'] = url
+                break
+        try:
+            kwargs['public_key'] = self.initial_data['publicKey']['publicKeyPem']
+        except KeyError:
+            pass
+        return kwargs
+
+    def build(self):
+        d = self.validated_data.copy()
+        d.update(self.prepare_missing_fields())
+        return self.Meta.model(**d)
+
+    def save(self, **kwargs):
+        kwargs.update(self.prepare_missing_fields())
+        return super().save(**kwargs)
+
+class ActorWebfingerSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = models.Actor
+        fields = ['url']
+
+    def to_representation(self, instance):
+        data = {}
+        data['subject'] = 'acct:{}'.format(instance.webfinger_subject)
+        data['links'] = [
+            {
+                'rel': 'self',
+                'href': instance.url,
+                'type': 'application/activity+json'
+            }
+        ]
+        data['aliases'] = [
+            instance.url
+        ]
+        return data
diff --git a/api/funkwhale_api/federation/urls.py b/api/funkwhale_api/federation/urls.py
index 5b7895451f815924589d1864b30e5ebd63166481..f2c6f4c78c61973436b3d92aacebdec3506156fd 100644
--- a/api/funkwhale_api/federation/urls.py
+++ b/api/funkwhale_api/federation/urls.py
@@ -4,9 +4,9 @@ from . import views
 
 router = routers.SimpleRouter(trailing_slash=False)
 router.register(
-    r'federation/instance',
-    views.InstanceViewSet,
-    'instance')
+    r'federation/instance/actors',
+    views.InstanceActorViewSet,
+    'instance-actors')
 router.register(
     r'.well-known',
     views.WellKnownViewSet,
diff --git a/api/funkwhale_api/federation/views.py b/api/funkwhale_api/federation/views.py
index 5f1ee36f76fada4bc3aa90e6941958fe1781f7a1..dcb806224314b00c026257cccb22d332aa671b7b 100644
--- a/api/funkwhale_api/federation/views.py
+++ b/api/funkwhale_api/federation/views.py
@@ -5,8 +5,9 @@ from django.http import HttpResponse
 from rest_framework import viewsets
 from rest_framework import views
 from rest_framework import response
-from rest_framework.decorators import list_route
+from rest_framework.decorators import list_route, detail_route
 
+from . import actors
 from . import renderers
 from . import serializers
 from . import webfinger
@@ -19,20 +20,30 @@ class FederationMixin(object):
         return super().dispatch(request, *args, **kwargs)
 
 
-class InstanceViewSet(FederationMixin, viewsets.GenericViewSet):
+class InstanceActorViewSet(FederationMixin, viewsets.GenericViewSet):
+    lookup_field = 'actor'
+    lookup_value_regex = '[a-z]*'
     authentication_classes = []
     permission_classes = []
     renderer_classes = [renderers.ActivityPubRenderer]
 
-    @list_route(methods=['get'])
-    def actor(self, request, *args, **kwargs):
-        return response.Response(serializers.repr_instance_actor())
+    def get_object(self):
+        try:
+            return actors.SYSTEM_ACTORS[self.kwargs['actor']]
+        except KeyError:
+            raise Http404
 
-    @list_route(methods=['get'])
+    def retrieve(self, request, *args, **kwargs):
+        actor_conf = self.get_object()
+        actor = actor_conf['get_actor']()
+        serializer = serializers.ActorSerializer(actor)
+        return response.Response(serializer.data, status=200)
+
+    @detail_route(methods=['get'])
     def inbox(self, request, *args, **kwargs):
         raise NotImplementedError()
 
-    @list_route(methods=['get'])
+    @detail_route(methods=['get'])
     def outbox(self, request, *args, **kwargs):
         raise NotImplementedError()
 
@@ -69,6 +80,5 @@ class WellKnownViewSet(FederationMixin, viewsets.GenericViewSet):
 
     def handler_acct(self, clean_result):
         username, hostname = clean_result
-        if username == 'service':
-            return webfinger.serialize_system_acct()
-        return {}
+        actor = actors.SYSTEM_ACTORS[username]['get_actor']()
+        return serializers.ActorWebfingerSerializer(actor).data
diff --git a/api/funkwhale_api/federation/webfinger.py b/api/funkwhale_api/federation/webfinger.py
index a9281c2b596899b7094ca659994441f9212f24e8..d698114f17f74e5d627d5e05a5f32d1074d2c889 100644
--- a/api/funkwhale_api/federation/webfinger.py
+++ b/api/funkwhale_api/federation/webfinger.py
@@ -2,7 +2,9 @@ from django import forms
 from django.conf import settings
 from django.urls import reverse
 
+from . import actors
 from . import utils
+
 VALID_RESOURCE_TYPES = ['acct']
 
 
@@ -30,23 +32,7 @@ def clean_acct(acct_string):
     if hostname != settings.FEDERATION_HOSTNAME:
         raise forms.ValidationError('Invalid hostname')
 
-    if username != 'service':
+    if username not in actors.SYSTEM_ACTORS:
         raise forms.ValidationError('Invalid username')
 
     return username, hostname
-
-
-def serialize_system_acct():
-    return {
-        'subject': 'acct:service@{}'.format(settings.FEDERATION_HOSTNAME),
-        'aliases': [
-            utils.full_url(reverse('federation:instance-actor'))
-        ],
-        'links': [
-            {
-                'rel': 'self',
-                'type': 'application/activity+json',
-                'href': utils.full_url(reverse('federation:instance-actor')),
-            }
-        ]
-    }
diff --git a/api/tests/federation/test_actors.py b/api/tests/federation/test_actors.py
new file mode 100644
index 0000000000000000000000000000000000000000..cd4b0b82fe87653378a9072d15335bbde02bf7b0
--- /dev/null
+++ b/api/tests/federation/test_actors.py
@@ -0,0 +1,42 @@
+from django.urls import reverse
+
+from funkwhale_api.federation import actors
+
+
+def test_actor_fetching(r_mock):
+    payload = {
+        'id': 'https://actor.mock/users/actor#main-key',
+        'owner': 'test',
+        'publicKeyPem': 'test_pem',
+    }
+    actor_url = 'https://actor.mock/'
+    r_mock.get(actor_url, json=payload)
+    r = actors.get_actor_data(actor_url)
+
+    assert r == payload
+
+
+def test_get_library(settings, preferences):
+    preferences['federation__public_key'] = 'public_key'
+    expected = {
+        'preferred_username': 'library',
+        'domain': settings.FEDERATION_HOSTNAME,
+        'type': 'Person',
+        'name': '{}\'s library'.format(settings.FEDERATION_HOSTNAME),
+        'manually_approves_followers': True,
+        'url': reverse(
+            'federation:instance-actors-detail',
+            kwargs={'actor': 'library'}),
+        'shared_inbox_url': reverse(
+            'federation:instance-actors-inbox',
+            kwargs={'actor': 'library'}),
+        'inbox_url': reverse(
+            'federation:instance-actors-inbox',
+            kwargs={'actor': 'library'}),
+        'public_key': 'public_key',
+        'summary': 'Bot account to federate with {}\'s library'.format(
+        settings.FEDERATION_HOSTNAME),
+    }
+    actor = actors.SYSTEM_ACTORS['library']['get_actor']()
+    for key, value in expected.items():
+        assert getattr(actor, key) == value
diff --git a/api/tests/federation/test_authentication.py b/api/tests/federation/test_authentication.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c4a25a7f07df8afb0dcb24855770641c0acbaa7
--- /dev/null
+++ b/api/tests/federation/test_authentication.py
@@ -0,0 +1,39 @@
+from funkwhale_api.federation import authentication
+from funkwhale_api.federation import keys
+from funkwhale_api.federation import signing
+
+
+def test_authenticate(nodb_factories, mocker, api_request):
+    private, public = keys.get_key_pair()
+    actor_url = 'https://test.federation/actor'
+    mocker.patch(
+        'funkwhale_api.federation.actors.get_actor_data',
+        return_value={
+            'id': actor_url,
+            'outbox': 'https://test.com',
+            'inbox': 'https://test.com',
+            'publicKey': {
+                'publicKeyPem': public.decode('utf-8'),
+                'owner': actor_url,
+                'id': actor_url + '#main-key',
+            }
+        })
+    signed_request = nodb_factories['federation.SignedRequest'](
+        auth__key=private,
+        auth__key_id=actor_url + '#main-key'
+    )
+    prepared = signed_request.prepare()
+    django_request = api_request.get(
+        '/',
+        headers={
+            'Date': prepared.headers['date'],
+            'Signature': prepared.headers['signature'],
+        }
+    )
+    authenticator = authentication.SignatureAuthentication()
+    user, _ = authenticator.authenticate(django_request)
+    actor = django_request.actor
+
+    assert user.is_anonymous is True
+    assert actor.public_key == public.decode('utf-8')
+    assert actor.url == actor_url
diff --git a/api/tests/federation/test_keys.py b/api/tests/federation/test_keys.py
index 1c30c30b17ed6450e7ec133498f3788be16a9ee6..9dd71be092bc39dcfb09f9b0e931c51a9ea37f5c 100644
--- a/api/tests/federation/test_keys.py
+++ b/api/tests/federation/test_keys.py
@@ -1,16 +1,25 @@
+import pytest
+
 from funkwhale_api.federation import keys
 
 
-def test_public_key_fetching(r_mock):
-    payload = {
-        'id': 'https://actor.mock/users/actor#main-key',
-        'owner': 'test',
-        'publicKeyPem': 'test_pem',
-    }
-    actor = 'https://actor.mock/'
-    r_mock.get(actor, json={'publicKey': payload})
-    r = keys.get_public_key(actor)
+@pytest.mark.parametrize('raw, expected', [
+    ('algorithm="test",keyId="https://test.com"', 'https://test.com'),
+    ('keyId="https://test.com",algorithm="test"', 'https://test.com'),
+])
+def test_get_key_from_header(raw, expected):
+    r = keys.get_key_id_from_signature_header(raw)
+    assert r == expected
+
 
-    assert r['id'] == payload['id']
-    assert r['owner'] == payload['owner']
-    assert r['public_key_pem'] == payload['publicKeyPem']
+@pytest.mark.parametrize('raw', [
+    'algorithm="test",keyid="badCase"',
+    'algorithm="test",wrong="wrong"',
+    'keyId = "wrong"',
+    'keyId=\'wrong\'',
+    'keyId="notanurl"',
+    'keyId="wrong://test.com"',
+])
+def test_get_key_from_header_invalid(raw):
+    with pytest.raises(ValueError):
+        keys.get_key_id_from_signature_header(raw)
diff --git a/api/tests/federation/test_serializers.py b/api/tests/federation/test_serializers.py
index 18b8525f281c3cbea6387bdd4a7336fd2b0d0b6b..efa92b16a26dcdf72287282895872c10e5d10070 100644
--- a/api/tests/federation/test_serializers.py
+++ b/api/tests/federation/test_serializers.py
@@ -1,36 +1,146 @@
 from django.urls import reverse
 
 from funkwhale_api.federation import keys
+from funkwhale_api.federation import models
 from funkwhale_api.federation import serializers
 
 
-def test_repr_instance_actor(db, preferences, settings):
-    _, public_key = keys.get_key_pair()
-    preferences['federation__public_key'] = public_key.decode('utf-8')
-    settings.FEDERATION_HOSTNAME = 'test.federation'
-    settings.FUNKWHALE_URL = 'https://test.federation'
-    actor_url = settings.FUNKWHALE_URL + reverse('federation:instance-actor')
-    inbox_url = settings.FUNKWHALE_URL + reverse('federation:instance-inbox')
-    outbox_url = settings.FUNKWHALE_URL + reverse('federation:instance-outbox')
+def test_actor_serializer_from_ap(db):
+    payload = {
+    	'id': 'https://test.federation/user',
+    	'type': 'Person',
+    	'following': 'https://test.federation/user/following',
+    	'followers': 'https://test.federation/user/followers',
+    	'inbox': 'https://test.federation/user/inbox',
+    	'outbox': 'https://test.federation/user/outbox',
+    	'preferredUsername': 'user',
+    	'name': 'Real User',
+    	'summary': 'Hello world',
+    	'url': 'https://test.federation/@user',
+    	'manuallyApprovesFollowers': False,
+    	'publicKey': {
+    		'id': 'https://test.federation/user#main-key',
+    		'owner': 'https://test.federation/user',
+    		'publicKeyPem': 'yolo'
+    	},
+    	'endpoints': {
+    		'sharedInbox': 'https://test.federation/inbox'
+    	},
+    }
+
+    serializer = serializers.ActorSerializer(data=payload)
+    assert serializer.is_valid()
+
+    actor = serializer.build()
+
+    assert actor.url == payload['id']
+    assert actor.inbox_url == payload['inbox']
+    assert actor.outbox_url == payload['outbox']
+    assert actor.shared_inbox_url == payload['endpoints']['sharedInbox']
+    assert actor.followers_url == payload['followers']
+    assert actor.following_url == payload['following']
+    assert actor.public_key == payload['publicKey']['publicKeyPem']
+    assert actor.preferred_username == payload['preferredUsername']
+    assert actor.name == payload['name']
+    assert actor.domain == 'test.federation'
+    assert actor.summary == payload['summary']
+    assert actor.type == 'Person'
+    assert actor.manually_approves_followers == payload['manuallyApprovesFollowers']
+
+
+def test_actor_serializer_only_mandatory_field_from_ap(db):
+    payload = {
+    	'id': 'https://test.federation/user',
+    	'type': 'Person',
+    	'following': 'https://test.federation/user/following',
+    	'followers': 'https://test.federation/user/followers',
+    	'inbox': 'https://test.federation/user/inbox',
+    	'outbox': 'https://test.federation/user/outbox',
+    	'preferredUsername': 'user',
+    }
+
+    serializer = serializers.ActorSerializer(data=payload)
+    assert serializer.is_valid()
+
+    actor = serializer.build()
 
+    assert actor.url == payload['id']
+    assert actor.inbox_url == payload['inbox']
+    assert actor.outbox_url == payload['outbox']
+    assert actor.followers_url == payload['followers']
+    assert actor.following_url == payload['following']
+    assert actor.preferred_username == payload['preferredUsername']
+    assert actor.domain == 'test.federation'
+    assert actor.type == 'Person'
+    assert actor.manually_approves_followers is None
+
+
+def test_actor_serializer_to_ap():
     expected = {
         '@context': [
             'https://www.w3.org/ns/activitystreams',
             'https://w3id.org/security/v1',
             {},
         ],
-        'id': actor_url,
-        'type': 'Person',
-        'preferredUsername': 'service',
-        'name': 'Service Bot - test.federation',
-        'summary': 'Bot account for federating with test.federation',
-        'inbox': inbox_url,
-        'outbox': outbox_url,
-        'publicKey': {
-            'id': '{}#main-key'.format(actor_url),
-            'owner': actor_url,
-            'publicKeyPem': public_key.decode('utf-8')
-        },
+    	'id': 'https://test.federation/user',
+    	'type': 'Person',
+    	'following': 'https://test.federation/user/following',
+    	'followers': 'https://test.federation/user/followers',
+    	'inbox': 'https://test.federation/user/inbox',
+    	'outbox': 'https://test.federation/user/outbox',
+    	'preferredUsername': 'user',
+    	'name': 'Real User',
+    	'summary': 'Hello world',
+    	'manuallyApprovesFollowers': False,
+    	'publicKey': {
+    		'id': 'https://test.federation/user#main-key',
+    		'owner': 'https://test.federation/user',
+    		'publicKeyPem': 'yolo'
+    	},
+    	'endpoints': {
+    		'sharedInbox': 'https://test.federation/inbox'
+    	},
+    }
+    ac = models.Actor(
+        url=expected['id'],
+        inbox_url=expected['inbox'],
+        outbox_url=expected['outbox'],
+        shared_inbox_url=expected['endpoints']['sharedInbox'],
+        followers_url=expected['followers'],
+        following_url=expected['following'],
+        public_key=expected['publicKey']['publicKeyPem'],
+        preferred_username=expected['preferredUsername'],
+        name=expected['name'],
+        domain='test.federation',
+        summary=expected['summary'],
+        type='Person',
+        manually_approves_followers=False,
+
+    )
+    serializer = serializers.ActorSerializer(ac)
+
+    assert serializer.data == expected
+
+
+def test_webfinger_serializer():
+    expected = {
+        'subject': 'acct:service@test.federation',
+        'links': [
+            {
+                'rel': 'self',
+                'href': 'https://test.federation/federation/instance/actor',
+                'type': 'application/activity+json',
+            }
+        ],
+        'aliases': [
+            'https://test.federation/federation/instance/actor',
+        ]
     }
+    actor = models.Actor(
+        url=expected['links'][0]['href'],
+        preferred_username='service',
+        domain='test.federation',
+    )
+    serializer = serializers.ActorWebfingerSerializer(actor)
 
-    assert expected == serializers.repr_instance_actor()
+    assert serializer.data == expected
diff --git a/api/tests/federation/test_views.py b/api/tests/federation/test_views.py
index 6a8de8c14a8af9944e23860feb2951f07c0a4fc1..96cf8ff7fb8ffdc78f668af49b0c12718e7f05fc 100644
--- a/api/tests/federation/test_views.py
+++ b/api/tests/federation/test_views.py
@@ -2,38 +2,43 @@ from django.urls import reverse
 
 import pytest
 
+from funkwhale_api.federation import actors
 from funkwhale_api.federation import serializers
 from funkwhale_api.federation import webfinger
 
 
-def test_instance_actor(db, settings, api_client):
-    settings.FUNKWHALE_URL = 'http://test.com'
-    url = reverse('federation:instance-actor')
+
+@pytest.mark.parametrize('system_actor', actors.SYSTEM_ACTORS.keys())
+def test_instance_actors(system_actor, db, settings, api_client):
+    actor = actors.SYSTEM_ACTORS[system_actor]['get_actor']()
+    url = reverse(
+        'federation:instance-actors-detail',
+        kwargs={'actor': system_actor})
     response = api_client.get(url)
+    serializer = serializers.ActorSerializer(actor)
 
     assert response.status_code == 200
-    assert response.data == serializers.repr_instance_actor()
-
-
-@pytest.mark.parametrize('route', [
-    'instance-outbox',
-    'instance-inbox',
-    'instance-actor',
-    'well-known-webfinger',
-])
-def test_instance_inbox_405_if_federation_disabled(
-        db, settings, api_client, route):
-    settings.FEDERATION_ENABLED = False
-    url = reverse('federation:{}'.format(route))
-    response = api_client.get(url)
+    assert response.data == serializer.data
 
-    assert response.status_code == 405
+
+# @pytest.mark.parametrize('route', [
+#     'instance-outbox',
+#     'instance-inbox',
+#     'instance-actor',
+#     'well-known-webfinger',
+# ])
+# def test_instance_inbox_405_if_federation_disabled(
+#         db, settings, api_client, route):
+#     settings.FEDERATION_ENABLED = False
+#     url = reverse('federation:{}'.format(route))
+#     response = api_client.get(url)
+#
+#     assert response.status_code == 405
 
 
 def test_wellknown_webfinger_validates_resource(
     db, api_client, settings, mocker):
     clean = mocker.spy(webfinger, 'clean_resource')
-    settings.FEDERATION_ENABLED = True
     url = reverse('federation:well-known-webfinger')
     response = api_client.get(url, data={'resource': 'something'})
 
@@ -45,14 +50,15 @@ def test_wellknown_webfinger_validates_resource(
     )
 
 
+@pytest.mark.parametrize('system_actor', actors.SYSTEM_ACTORS.keys())
 def test_wellknown_webfinger_system(
-    db, api_client, settings, mocker):
-    settings.FEDERATION_ENABLED = True
-    settings.FEDERATION_HOSTNAME = 'test.federation'
+        system_actor, db, api_client, settings, mocker):
+    actor = actors.SYSTEM_ACTORS[system_actor]['get_actor']()
     url = reverse('federation:well-known-webfinger')
     response = api_client.get(
-        url, data={'resource': 'acct:service@test.federation'})
+        url, data={'resource': 'acct:{}'.format(actor.webfinger_subject)})
+    serializer = serializers.ActorWebfingerSerializer(actor)
 
     assert response.status_code == 200
     assert response['Content-Type'] == 'application/jrd+json'
-    assert response.data == webfinger.serialize_system_acct()
+    assert response.data == serializer.data
diff --git a/api/tests/federation/test_webfinger.py b/api/tests/federation/test_webfinger.py
index d2b00f8f1c61afee1133c31749f25af1ce6522a4..fd1cb1d058cfb43070fed1db6c32caf121431927 100644
--- a/api/tests/federation/test_webfinger.py
+++ b/api/tests/federation/test_webfinger.py
@@ -25,9 +25,8 @@ def test_webfinger_clean_resource_errors(resource, message):
 
 
 def test_webfinger_clean_acct(settings):
-    settings.FEDERATION_HOSTNAME = 'test.federation'
-    username, hostname = webfinger.clean_acct('service@test.federation')
-    assert username == 'service'
+    username, hostname = webfinger.clean_acct('library@test.federation')
+    assert username == 'library'
     assert hostname == 'test.federation'
 
 
@@ -37,30 +36,7 @@ def test_webfinger_clean_acct(settings):
     ('noop@test.federation', 'Invalid account'),
 ])
 def test_webfinger_clean_acct_errors(resource, message, settings):
-    settings.FEDERATION_HOSTNAME = 'test.federation'
-
     with pytest.raises(forms.ValidationError) as excinfo:
         webfinger.clean_resource(resource)
 
         assert message == str(excinfo)
-
-
-def test_service_serializer(settings):
-    settings.FEDERATION_HOSTNAME = 'test.federation'
-    settings.FUNKWHALE_URL = 'https://test.federation'
-
-    expected = {
-        'subject': 'acct:service@test.federation',
-        'links': [
-            {
-                'rel': 'self',
-                'href': 'https://test.federation/federation/instance/actor',
-                'type': 'application/activity+json',
-            }
-        ],
-        'aliases': [
-            'https://test.federation/federation/instance/actor',
-        ]
-    }
-
-    assert expected == webfinger.serialize_system_acct()