diff --git a/api/funkwhale_api/audio/factories.py b/api/funkwhale_api/audio/factories.py
index 9629b2a1eecea45b16ac1486c02b1e233553b54b..6a7c5674529f398ccc634fb8686017e168840111 100644
--- a/api/funkwhale_api/audio/factories.py
+++ b/api/funkwhale_api/audio/factories.py
@@ -25,6 +25,7 @@ class ChannelFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
         music_factories.ArtistFactory,
         attributed_to=factory.SelfAttribute("..attributed_to"),
     )
+    rss_url = factory.Faker("url")
     metadata = factory.LazyAttribute(lambda o: {})
 
     class Meta:
diff --git a/api/funkwhale_api/audio/migrations/0003_channel_rss_url.py b/api/funkwhale_api/audio/migrations/0003_channel_rss_url.py
new file mode 100644
index 0000000000000000000000000000000000000000..9936883f75f2b157be60e0d9c6e79a53e1e6c4a4
--- /dev/null
+++ b/api/funkwhale_api/audio/migrations/0003_channel_rss_url.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.10 on 2020-02-06 15:01
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('audio', '0002_channel_metadata'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='channel',
+            name='rss_url',
+            field=models.URLField(blank=True, max_length=500, null=True),
+        ),
+    ]
diff --git a/api/funkwhale_api/audio/models.py b/api/funkwhale_api/audio/models.py
index 7acca926a691e293332311479813db5246321812..e95bb5d66ff0b3b3987ecf6c9a5389c746fb50aa 100644
--- a/api/funkwhale_api/audio/models.py
+++ b/api/funkwhale_api/audio/models.py
@@ -36,6 +36,7 @@ class Channel(models.Model):
         "music.Library", on_delete=models.CASCADE, related_name="channel"
     )
     creation_date = models.DateTimeField(default=timezone.now)
+    rss_url = models.URLField(max_length=500, null=True, blank=True)
 
     # metadata to enhance rss feed
     metadata = JSONField(
@@ -46,6 +47,9 @@ class Channel(models.Model):
         return federation_utils.full_url("/channels/{}".format(self.uuid))
 
     def get_rss_url(self):
+        if not self.artist.is_local:
+            return self.rss_url
+
         return federation_utils.full_url(
             reverse("api:v1:channels-rss", kwargs={"uuid": self.uuid})
         )
diff --git a/api/funkwhale_api/audio/serializers.py b/api/funkwhale_api/audio/serializers.py
index 2d46b8cce4115507ec61a3035c50c68e7c11191c..3f953436cb363f0723912e822d56353141f9685d 100644
--- a/api/funkwhale_api/audio/serializers.py
+++ b/api/funkwhale_api/audio/serializers.py
@@ -191,6 +191,7 @@ class ChannelSerializer(serializers.ModelSerializer):
     artist = serializers.SerializerMethodField()
     actor = federation_serializers.APIActorSerializer()
     attributed_to = federation_serializers.APIActorSerializer()
+    rss_url = serializers.CharField(source="get_rss_url")
 
     class Meta:
         model = models.Channel
@@ -201,6 +202,7 @@ class ChannelSerializer(serializers.ModelSerializer):
             "actor",
             "creation_date",
             "metadata",
+            "rss_url",
         ]
 
     def get_artist(self, obj):
diff --git a/api/funkwhale_api/federation/jsonld.py b/api/funkwhale_api/federation/jsonld.py
index c0170b2350e7815c4b0d45dfb0d4bf4534e54ef6..450490c910ff072927c1b2311aa9b45f72700359 100644
--- a/api/funkwhale_api/federation/jsonld.py
+++ b/api/funkwhale_api/federation/jsonld.py
@@ -167,7 +167,7 @@ def prepare_for_serializer(payload, config, fallbacks={}):
                 attr=field_config.get("attr"),
             )
         except (IndexError, KeyError):
-            aliases = field_config.get("aliases", [])
+            aliases = field_config.get("aliases", {})
             noop = object()
             value = noop
             if not aliases:
@@ -176,9 +176,7 @@ def prepare_for_serializer(payload, config, fallbacks={}):
             for a in aliases:
                 try:
                     value = get_value(
-                        payload[a],
-                        keep=field_config.get("keep"),
-                        attr=field_config.get("attr"),
+                        payload[a["property"]], keep=a.get("keep"), attr=a.get("attr"),
                     )
                 except (IndexError, KeyError):
                     continue
@@ -231,14 +229,20 @@ def get_default_context_fw():
 
 
 class JsonLdSerializer(serializers.Serializer):
+    def __init__(self, *args, **kwargs):
+        self.jsonld_expand = kwargs.pop("jsonld_expand", True)
+        super().__init__(*args, **kwargs)
+
     def run_validation(self, data=empty):
-        if data and data is not empty and self.context.get("expand", True):
-            try:
-                data = expand(data)
-            except ValueError:
-                raise serializers.ValidationError(
-                    "{} is not a valid jsonld document".format(data)
-                )
+        if data and data is not empty:
+
+            if self.context.get("expand", self.jsonld_expand):
+                try:
+                    data = expand(data)
+                except ValueError:
+                    raise serializers.ValidationError(
+                        "{} is not a valid jsonld document".format(data)
+                    )
             try:
                 config = self.Meta.jsonld_mapping
             except AttributeError:
@@ -247,6 +251,7 @@ class JsonLdSerializer(serializers.Serializer):
                 fallbacks = self.Meta.jsonld_fallbacks
             except AttributeError:
                 fallbacks = {}
+
             data = prepare_for_serializer(data, config, fallbacks=fallbacks)
             dereferenced_fields = [
                 k
diff --git a/api/funkwhale_api/federation/models.py b/api/funkwhale_api/federation/models.py
index 016e712c1500ec589d8658e1b749ab7ff135f06a..c200e4f6efd6e70f9521bea38707d4c19153a264 100644
--- a/api/funkwhale_api/federation/models.py
+++ b/api/funkwhale_api/federation/models.py
@@ -254,6 +254,17 @@ class Actor(models.Model):
         except ObjectDoesNotExist:
             return None
 
+    def get_channel(self):
+        try:
+            return self.channel
+        except ObjectDoesNotExist:
+            return None
+
+    def get_absolute_url(self):
+        if self.is_local:
+            return federation_utils.full_url("/@{}".format(self.preferred_username))
+        return self.url or self.fid
+
     def get_current_usage(self):
         actor = self.__class__.objects.filter(pk=self.pk).with_current_usage().get()
         data = {}
diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py
index 6501e93cf8c9bf13c36803a3b2281d2c042e3892..9b53c3298e77e5b488c30f1c9265d6ef6e4adbb7 100644
--- a/api/funkwhale_api/federation/serializers.py
+++ b/api/funkwhale_api/federation/serializers.py
@@ -91,6 +91,17 @@ class ImageSerializer(MediaSerializer):
         return validated_data
 
 
+class URLSerializer(jsonld.JsonLdSerializer):
+    href = serializers.URLField(max_length=500)
+    mediaType = serializers.CharField(required=False)
+
+    class Meta:
+        jsonld_mapping = {
+            "href": jsonld.first_id(contexts.AS.href, aliases=[jsonld.raw("@id")]),
+            "mediaType": jsonld.first_val(contexts.AS.mediaType),
+        }
+
+
 class EndpointsSerializer(jsonld.JsonLdSerializer):
     sharedInbox = serializers.URLField(max_length=500, required=False)
 
@@ -105,10 +116,19 @@ class PublicKeySerializer(jsonld.JsonLdSerializer):
         jsonld_mapping = {"publicKeyPem": jsonld.first_val(contexts.SEC.publicKeyPem)}
 
 
+def get_by_media_type(urls, media_type):
+    for url in urls:
+        if url.get("mediaType", "text/html") == media_type:
+            return url
+
+
 class ActorSerializer(jsonld.JsonLdSerializer):
     id = serializers.URLField(max_length=500)
     outbox = serializers.URLField(max_length=500, required=False)
     inbox = serializers.URLField(max_length=500, required=False)
+    url = serializers.ListField(
+        child=URLSerializer(jsonld_expand=False), required=False, min_length=0
+    )
     type = serializers.ChoiceField(
         choices=[getattr(contexts.AS, c[0]) for c in models.TYPE_CHOICES]
     )
@@ -144,6 +164,7 @@ class ActorSerializer(jsonld.JsonLdSerializer):
             "mediaType": jsonld.first_val(contexts.AS.mediaType),
             "endpoints": jsonld.first_obj(contexts.AS.endpoints),
             "icon": jsonld.first_obj(contexts.AS.icon),
+            "url": jsonld.raw(contexts.AS.url),
         }
 
     def to_representation(self, instance):
@@ -165,6 +186,36 @@ class ActorSerializer(jsonld.JsonLdSerializer):
 
         if instance.summary_obj_id:
             ret["summary"] = instance.summary_obj.rendered
+        urls = []
+        if instance.url:
+            urls.append(
+                {"type": "Link", "href": instance.url, "mediaType": "text/html"}
+            )
+
+        channel = instance.get_channel()
+        if channel:
+            ret["url"] = [
+                {
+                    "type": "Link",
+                    "href": instance.channel.get_absolute_url()
+                    if instance.channel.artist.is_local
+                    else instance.get_absolute_url(),
+                    "mediaType": "text/html",
+                },
+                {
+                    "type": "Link",
+                    "href": instance.channel.get_rss_url(),
+                    "mediaType": "application/rss+xml",
+                },
+            ]
+        else:
+            ret["url"] = [
+                {
+                    "type": "Link",
+                    "href": instance.get_absolute_url(),
+                    "mediaType": "text/html",
+                }
+            ]
 
         ret["@context"] = jsonld.get_default_context()
         if instance.public_key:
@@ -192,6 +243,10 @@ class ActorSerializer(jsonld.JsonLdSerializer):
             "name": self.validated_data.get("name"),
             "preferred_username": self.validated_data["preferredUsername"],
         }
+        url = get_by_media_type(self.validated_data.get("url", []), "text/html")
+        if url:
+            kwargs["url"] = url["href"]
+
         maf = self.validated_data.get("manuallyApprovesFollowers")
         if maf is not None:
             kwargs["manually_approves_followers"] = maf
diff --git a/api/tests/audio/test_models.py b/api/tests/audio/test_models.py
index 5992e41ed78809bc268abb8a215cad8f0226ca5b..8a0d6e4b82b4a555669b1643ef0f5592ec2e9d0a 100644
--- a/api/tests/audio/test_models.py
+++ b/api/tests/audio/test_models.py
@@ -1,3 +1,8 @@
+from django.urls import reverse
+
+from funkwhale_api.federation import utils as federation_utils
+
+
 def test_channel(factories, now):
     channel = factories["audio.Channel"]()
     assert channel.artist is not None
@@ -5,3 +10,16 @@ def test_channel(factories, now):
     assert channel.attributed_to is not None
     assert channel.library is not None
     assert channel.creation_date >= now
+
+
+def test_channel_get_rss_url_local(factories):
+    channel = factories["audio.Channel"](artist__local=True)
+    expected = federation_utils.full_url(
+        reverse("api:v1:channels-rss", kwargs={"uuid": channel.uuid})
+    )
+    assert channel.get_rss_url() == expected
+
+
+def test_channel_get_rss_url_remote(factories):
+    channel = factories["audio.Channel"]()
+    assert channel.get_rss_url() == channel.rss_url
diff --git a/api/tests/audio/test_serializers.py b/api/tests/audio/test_serializers.py
index 9ef01c908aadd657eee08b19268315e4aa53107e..430673d6349099f0fe0e3ae138393c6298885283 100644
--- a/api/tests/audio/test_serializers.py
+++ b/api/tests/audio/test_serializers.py
@@ -134,6 +134,7 @@ def test_channel_serializer_representation(factories, to_api_date):
             channel.attributed_to
         ).data,
         "metadata": {},
+        "rss_url": channel.get_rss_url(),
     }
     expected["artist"]["description"] = common_serializers.ContentSerializer(
         content
diff --git a/api/tests/federation/test_serializers.py b/api/tests/federation/test_serializers.py
index d5a781bd6ea2f755e032c058dd160c625f66c1f5..c6f7eb0601771753d6c7712624e1f7176c1e5c72 100644
--- a/api/tests/federation/test_serializers.py
+++ b/api/tests/federation/test_serializers.py
@@ -30,6 +30,7 @@ def test_actor_serializer_from_ap(db):
         "name": "Test",
         "summary": "Hello world",
         "manuallyApprovesFollowers": True,
+        "url": "http://hello.world/path",
         "publicKey": {
             "publicKeyPem": public.decode("utf-8"),
             "owner": actor_url,
@@ -48,7 +49,7 @@ def test_actor_serializer_from_ap(db):
     actor = serializer.save()
 
     assert actor.fid == actor_url
-    assert actor.url is None
+    assert actor.url == payload["url"]
     assert actor.inbox_url == payload["inbox"]
     assert actor.shared_inbox_url == payload["endpoints"]["sharedInbox"]
     assert actor.outbox_url == payload["outbox"]
@@ -108,6 +109,7 @@ def test_actor_serializer_to_ap(factories):
         "outbox": "https://test.federation/user/outbox",
         "preferredUsername": "user",
         "name": "Real User",
+        "url": [{"type": "Link", "href": "https://test.url", "mediaType": "text/html"}],
         "manuallyApprovesFollowers": False,
         "publicKey": {
             "id": "https://test.federation/user#main-key",
@@ -120,13 +122,14 @@ def test_actor_serializer_to_ap(factories):
         fid=expected["id"],
         inbox_url=expected["inbox"],
         outbox_url=expected["outbox"],
+        url=expected["url"][0]["href"],
         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=models.Domain.objects.create(pk="test.federation"),
+        domain=models.Domain.objects.create(pk="test.domain"),
         type="Person",
         manually_approves_followers=False,
         attachment_icon=factories["common.Attachment"](),
@@ -1112,7 +1115,7 @@ def test_activity_pub_audio_serializer_to_ap(factories):
     assert serializer.data == expected
 
 
-def test_local_actor_serializer_to_ap(factories):
+def test_local_actor_serializer_to_ap(factories, settings):
     expected = {
         "@context": jsonld.get_default_context(),
         "id": "https://test.federation/user",
@@ -1155,6 +1158,15 @@ def test_local_actor_serializer_to_ap(factories):
     user.save()
     ac.refresh_from_db()
     expected["summary"] = content.rendered
+    expected["url"] = [
+        {
+            "type": "Link",
+            "href": "https://{}/@{}".format(
+                settings.FUNKWHALE_HOSTNAME, ac.preferred_username
+            ),
+            "mediaType": "text/html",
+        }
+    ]
     expected["icon"] = {
         "type": "Image",
         "mediaType": "image/jpeg",
@@ -1197,6 +1209,25 @@ def test_track_serializer_update_license(factories):
     assert obj.license_id == "cc-by-2.0"
 
 
+def test_channel_actor_serializer(factories):
+    channel = factories["audio.Channel"]()
+    serializer = serializers.ActorSerializer(channel.actor)
+    expected_url = [
+        {
+            "type": "Link",
+            "href": channel.actor.get_absolute_url(),
+            "mediaType": "text/html",
+        },
+        {
+            "type": "Link",
+            "href": channel.get_rss_url(),
+            "mediaType": "application/rss+xml",
+        },
+    ]
+
+    assert serializer.data["url"] == expected_url
+
+
 def test_channel_actor_outbox_serializer(factories):
     channel = factories["audio.Channel"]()
     uploads = factories["music.Upload"].create_batch(