diff --git a/api/funkwhale_api/federation/jsonld.py b/api/funkwhale_api/federation/jsonld.py
index 5101faeb6bf33087a2b04b1384dbbf22d6026800..8dce903891c41a6c6db7cd01aad9d43443af286b 100644
--- a/api/funkwhale_api/federation/jsonld.py
+++ b/api/funkwhale_api/federation/jsonld.py
@@ -53,6 +53,8 @@ class JsonLdField(serializers.ListField):
         self.id_only = kwargs.pop("id_only", False)
         self.base_field = kwargs.pop("base_field")
         super().__init__(*args, **kwargs)
+        self.child = self.base_field
+        self.child.parent = self
         if self.single:
             self.validators = [
                 v
@@ -105,12 +107,17 @@ class Serializer(serializers.Serializer):
             )
 
     def run_validation(self, initial_data):
+        initial_data.setdefault("@context", ns.CONTEXTS)
+        import ipdb
+
+        ipdb.set_trace()
         try:
             document = expand(initial_data)[0]
         except IndexError:
             raise serializers.ValidationError(
                 "Cannot parse json-ld, maybe you are missing context."
             )
+
         return super().run_validation(document)
 
     def validate(self, validated_data):
diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py
index c6a49233b3d12fae79b1269e04d40b65d05251dd..27dc4e4960360ef43caf53924d6f1d16d4116e1d 100644
--- a/api/funkwhale_api/federation/serializers.py
+++ b/api/funkwhale_api/federation/serializers.py
@@ -367,18 +367,37 @@ class APILibraryTrackSerializer(serializers.ModelSerializer):
         return "not_imported"
 
 
-class FollowSerializer(serializers.Serializer):
-    id = serializers.URLField(max_length=500)
-    object = serializers.URLField(max_length=500)
-    actor = serializers.URLField(max_length=500)
-    type = serializers.ChoiceField(choices=["Follow"])
+class FollowSerializer(jsonld.Serializer):
+    valid_types = [ns.SHORT["as:Follow"]]
+    jsonld_fields = [
+        (
+            ns.SHORT["as:actor"],
+            ActorSerializer(required=True),
+            {"source": "actor", "single": True},
+        ),
+        (
+            ns.SHORT["as:object"],
+            ActorSerializer(required=True),
+            {"source": "object", "single": True},
+        ),
+    ]
+
+    def validate(self, validated_data):
+        validated_data = super().validate(validated_data)
+        validated_data[ns.SHORT["as:object"]] = self.validate_object(
+            validated_data[ns.SHORT["as:object"]]
+        )
+        validated_data[ns.SHORT["as:actor"]] = self.validate_actor(
+            validated_data[ns.SHORT["as:actor"]]
+        )
+        return validated_data
 
     def validate_object(self, v):
         expected = self.context.get("follow_target")
         if expected and expected.url != v:
             raise serializers.ValidationError("Invalid target")
         try:
-            return models.Actor.objects.get(url=v)
+            return models.Actor.objects.get(url=v["@id"])
         except models.Actor.DoesNotExist:
             raise serializers.ValidationError("Target not found")
 
@@ -387,20 +406,20 @@ class FollowSerializer(serializers.Serializer):
         if expected and expected.url != v:
             raise serializers.ValidationError("Invalid actor")
         try:
-            return models.Actor.objects.get(url=v)
+            return models.Actor.objects.get(url=v["@id"])
         except models.Actor.DoesNotExist:
             raise serializers.ValidationError("Actor not found")
 
     def save(self, **kwargs):
         return models.Follow.objects.get_or_create(
-            actor=self.validated_data["actor"],
-            target=self.validated_data["object"],
+            actor=self.validated_data[ns.SHORT["as:actor"]],
+            target=self.validated_data[ns.SHORT["as:object"]],
             **kwargs,  # noqa
         )[0]
 
     def to_representation(self, instance):
         return {
-            "@context": AP_CONTEXT,
+            "@context": ns.CONTEXTS,
             "actor": instance.actor.url,
             "id": instance.get_federation_url(),
             "object": instance.target.url,
@@ -425,30 +444,45 @@ class APIFollowSerializer(serializers.ModelSerializer):
         ]
 
 
-class AcceptFollowSerializer(serializers.Serializer):
-    id = serializers.URLField(max_length=500)
-    actor = serializers.URLField(max_length=500)
-    object = FollowSerializer()
-    type = serializers.ChoiceField(choices=["Accept"])
+class AcceptFollowSerializer(jsonld.Serializer):
+    valid_types = [ns.SHORT["as:Accept"]]
+    jsonld_fields = [
+        (
+            ns.SHORT["as:actor"],
+            ActorSerializer(required=True),
+            {"source": "actor", "single": True},
+        ),
+        (
+            ns.SHORT["as:object"],
+            FollowSerializer(required=True),
+            {"source": "object", "single": True},
+        ),
+    ]
 
     def validate_actor(self, v):
         expected = self.context.get("follow_target")
         if expected and expected.url != v:
             raise serializers.ValidationError("Invalid actor")
         try:
-            return models.Actor.objects.get(url=v)
+            return models.Actor.objects.get(url=v["@id"])
         except models.Actor.DoesNotExist:
             raise serializers.ValidationError("Actor not found")
 
     def validate(self, validated_data):
+        import ipdb; ipdb.set_trace()
+        validated_data = super().validate(validated_data)
+        validated_data[ns.SHORT["as:actor"]] = self.validate_actor(
+            validated_data[ns.SHORT["as:actor"]]
+        )
+        follow_target = validated_data[ns.SHORT["as:object"]][ns.SHORT["as:object"]]
+        follow_actor = validated_data[ns.SHORT["as:object"]][ns.SHORT["as:actor"]]
         # we ensure the accept actor actually match the follow target
-        if validated_data["actor"] != validated_data["object"]["object"]:
+        if validated_data[ns.SHORT["as:actor"]].url != follow_target["@id"]:
             raise serializers.ValidationError("Actor mismatch")
         try:
             validated_data["follow"] = (
                 models.Follow.objects.filter(
-                    target=validated_data["actor"],
-                    actor=validated_data["object"]["actor"],
+                    target=validated_data[ns.SHORT["as:actor"]], actor=follow_actor
                 )
                 .exclude(approved=True)
                 .get()
diff --git a/api/tests/federation/test_serializers.py b/api/tests/federation/test_serializers.py
index 0ea3c25a3001690815f14336d4414d0f9b4f42fb..a78e5bc8232e1de0f2c433108a4e6ddf92bf27dc 100644
--- a/api/tests/federation/test_serializers.py
+++ b/api/tests/federation/test_serializers.py
@@ -142,11 +142,7 @@ def test_follow_serializer_to_ap(factories):
     serializer = serializers.FollowSerializer(follow)
 
     expected = {
-        "@context": [
-            "https://www.w3.org/ns/activitystreams",
-            "https://w3id.org/security/v1",
-            {},
-        ],
+        "@context": ns.CONTEXTS,
         "id": follow.get_federation_url(),
         "type": "Follow",
         "actor": follow.actor.url,
@@ -161,6 +157,7 @@ def test_follow_serializer_save(factories):
     target = factories["federation.Actor"]()
 
     data = {
+        "@context": ns.CONTEXTS,
         "id": "https://test.follow",
         "type": "Follow",
         "actor": actor.url,
@@ -184,6 +181,7 @@ def test_follow_serializer_save_validates_on_context(factories):
     impostor = factories["federation.Actor"]()
 
     data = {
+        "@context": ns.CONTEXTS,
         "id": "https://test.follow",
         "type": "Follow",
         "actor": actor.url,
@@ -203,11 +201,7 @@ def test_accept_follow_serializer_representation(factories):
     follow = factories["federation.Follow"](approved=None)
 
     expected = {
-        "@context": [
-            "https://www.w3.org/ns/activitystreams",
-            "https://w3id.org/security/v1",
-            {},
-        ],
+        "@context": ns.CONTEXTS,
         "id": follow.get_federation_url() + "/accept",
         "type": "Accept",
         "actor": follow.target.url,
@@ -223,11 +217,7 @@ def test_accept_follow_serializer_save(factories):
     follow = factories["federation.Follow"](approved=None)
 
     data = {
-        "@context": [
-            "https://www.w3.org/ns/activitystreams",
-            "https://w3id.org/security/v1",
-            {},
-        ],
+        "@context": ns.CONTEXTS,
         "id": follow.get_federation_url() + "/accept",
         "type": "Accept",
         "actor": follow.target.url,
@@ -247,11 +237,7 @@ def test_accept_follow_serializer_validates_on_context(factories):
     follow = factories["federation.Follow"](approved=None)
     impostor = factories["federation.Actor"]()
     data = {
-        "@context": [
-            "https://www.w3.org/ns/activitystreams",
-            "https://w3id.org/security/v1",
-            {},
-        ],
+        "@context": ns.CONTEXTS,
         "id": follow.get_federation_url() + "/accept",
         "type": "Accept",
         "actor": impostor.url,
@@ -271,11 +257,7 @@ def test_undo_follow_serializer_representation(factories):
     follow = factories["federation.Follow"](approved=True)
 
     expected = {
-        "@context": [
-            "https://www.w3.org/ns/activitystreams",
-            "https://w3id.org/security/v1",
-            {},
-        ],
+        "@context": ns.CONTEXTS,
         "id": follow.get_federation_url() + "/undo",
         "type": "Undo",
         "actor": follow.actor.url,
@@ -291,11 +273,7 @@ def test_undo_follow_serializer_save(factories):
     follow = factories["federation.Follow"](approved=True)
 
     data = {
-        "@context": [
-            "https://www.w3.org/ns/activitystreams",
-            "https://w3id.org/security/v1",
-            {},
-        ],
+        "@context": ns.CONTEXTS,
         "id": follow.get_federation_url() + "/undo",
         "type": "Undo",
         "actor": follow.actor.url,
@@ -314,11 +292,7 @@ def test_undo_follow_serializer_validates_on_context(factories):
     follow = factories["federation.Follow"](approved=True)
     impostor = factories["federation.Actor"]()
     data = {
-        "@context": [
-            "https://www.w3.org/ns/activitystreams",
-            "https://w3id.org/security/v1",
-            {},
-        ],
+        "@context": ns.CONTEXTS,
         "id": follow.get_federation_url() + "/undo",
         "type": "Undo",
         "actor": impostor.url,