diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts
index f2fdb66bd0..1c97dc2980 100644
--- a/packages/backend-rs/index.d.ts
+++ b/packages/backend-rs/index.d.ts
@@ -111,6 +111,13 @@ export interface ApAccept {
   object: ApFollow
 }
 
+export interface ApAdd {
+  type: ApObject
+  actor: string
+  target: string
+  object: string
+}
+
 export interface ApEmoji {
   id: string
   type: ApObject
@@ -146,6 +153,7 @@ export interface ApMention {
 }
 
 export type ApObject =  'Accept'|
+'Add'|
 'Emoji'|
 'Flag'|
 'Follow'|
@@ -153,6 +161,7 @@ export type ApObject =  'Accept'|
 'Mention'|
 'Image'|
 'Read'|
+'Remove'|
 'Tombstone';
 
 export interface App {
@@ -172,6 +181,13 @@ export interface ApRead {
   object: string
 }
 
+export interface ApRemove {
+  type: ApObject
+  actor: string
+  target: string
+  object: string
+}
+
 export interface ApTombstone {
   id: string
   type: ApObject
@@ -1328,6 +1344,8 @@ export declare function removeOldAttestationChallenges(): Promise<void>
 
 export declare function renderAccept(userId: string, followObject: ApFollow): ApAccept
 
+export declare function renderAdd(userId: string, noteId: string): ApAdd
+
 export declare function renderEmoji(emoji: Emoji): ApEmoji
 
 export declare function renderFlag(targetUserUri: string, comment: string): Promise<ApFlag>
@@ -1342,6 +1360,8 @@ export declare function renderMention(user: UserLike): ApMention
 
 export declare function renderRead(userId: string, messageUri: string): ApRead
 
+export declare function renderRemove(userId: string, noteId: string): ApRemove
+
 export declare function renderTombstone(noteId: string): ApTombstone
 
 export interface RenoteMuting {
diff --git a/packages/backend-rs/index.js b/packages/backend-rs/index.js
index 0ef803af9e..bd87db3932 100644
--- a/packages/backend-rs/index.js
+++ b/packages/backend-rs/index.js
@@ -439,6 +439,7 @@ module.exports.PushSubscriptionType = nativeBinding.PushSubscriptionType
 module.exports.RelayStatus = nativeBinding.RelayStatus
 module.exports.removeOldAttestationChallenges = nativeBinding.removeOldAttestationChallenges
 module.exports.renderAccept = nativeBinding.renderAccept
+module.exports.renderAdd = nativeBinding.renderAdd
 module.exports.renderEmoji = nativeBinding.renderEmoji
 module.exports.renderFlag = nativeBinding.renderFlag
 module.exports.renderFollow = nativeBinding.renderFollow
@@ -446,6 +447,7 @@ module.exports.renderFollowRelay = nativeBinding.renderFollowRelay
 module.exports.renderHashtag = nativeBinding.renderHashtag
 module.exports.renderMention = nativeBinding.renderMention
 module.exports.renderRead = nativeBinding.renderRead
+module.exports.renderRemove = nativeBinding.renderRemove
 module.exports.renderTombstone = nativeBinding.renderTombstone
 module.exports.safeForSql = nativeBinding.safeForSql
 module.exports.sendPushNotification = nativeBinding.sendPushNotification
diff --git a/packages/backend-rs/src/federation/activitypub/object/add.rs b/packages/backend-rs/src/federation/activitypub/object/add.rs
new file mode 100644
index 0000000000..4d34ae3b1f
--- /dev/null
+++ b/packages/backend-rs/src/federation/activitypub/object/add.rs
@@ -0,0 +1,34 @@
+//! Add note to featured collection (pinned posts)
+
+use super::*;
+use crate::misc::{note, user};
+
+#[macros::export(object)]
+pub struct ApAdd {
+    pub r#type: ApObject,
+    pub actor: String,
+    pub target: String,
+    pub object: String,
+}
+
+impl ActivityPubObject for ApAdd {}
+
+impl ApAdd {
+    #[allow(dead_code)] // TODO: remove this line
+    fn new(user_id: String, note_id: String) -> Self {
+        let actor_uri = user::local_uri(user_id);
+        let collection_uri = format!("{}/collections/featured", actor_uri);
+
+        Self {
+            r#type: ApObject::Add,
+            actor: actor_uri,
+            target: collection_uri,
+            object: note::local_uri(note_id),
+        }
+    }
+}
+
+#[macros::ts_export]
+pub fn render_add(user_id: String, note_id: String) -> ApAdd {
+    ApAdd::new(user_id, note_id)
+}
diff --git a/packages/backend-rs/src/federation/activitypub/object/mod.rs b/packages/backend-rs/src/federation/activitypub/object/mod.rs
index 7745031907..179674da0b 100644
--- a/packages/backend-rs/src/federation/activitypub/object/mod.rs
+++ b/packages/backend-rs/src/federation/activitypub/object/mod.rs
@@ -1,10 +1,12 @@
 pub mod accept;
+pub mod add;
 pub mod emoji;
 pub mod flag;
 pub mod follow;
 pub mod hashtag;
 pub mod mention;
 pub mod read;
+pub mod remove;
 pub mod tombstone;
 
 pub trait ActivityPubObject {}
@@ -12,6 +14,7 @@ pub trait ActivityPubObject {}
 #[macros::export(string_enum)]
 pub enum ApObject {
     Accept,
+    Add,
     Emoji,
     Flag,
     Follow,
@@ -19,6 +22,7 @@ pub enum ApObject {
     Mention,
     Image,
     Read,
+    Remove,
     Tombstone,
 }
 
diff --git a/packages/backend-rs/src/federation/activitypub/object/remove.rs b/packages/backend-rs/src/federation/activitypub/object/remove.rs
new file mode 100644
index 0000000000..42af021fca
--- /dev/null
+++ b/packages/backend-rs/src/federation/activitypub/object/remove.rs
@@ -0,0 +1,34 @@
+//! Remove note from featured collection (pinned posts)
+
+use super::*;
+use crate::misc::{note, user};
+
+#[macros::export(object)]
+pub struct ApRemove {
+    pub r#type: ApObject,
+    pub actor: String,
+    pub target: String,
+    pub object: String,
+}
+
+impl ActivityPubObject for ApRemove {}
+
+impl ApRemove {
+    #[allow(dead_code)] // TODO: remove this line
+    fn new(user_id: String, note_id: String) -> Self {
+        let actor_uri = user::local_uri(user_id);
+        let collection_uri = format!("{}/collections/featured", actor_uri);
+
+        Self {
+            r#type: ApObject::Remove,
+            actor: actor_uri,
+            target: collection_uri,
+            object: note::local_uri(note_id),
+        }
+    }
+}
+
+#[macros::ts_export]
+pub fn render_remove(user_id: String, note_id: String) -> ApRemove {
+    ApRemove::new(user_id, note_id)
+}
diff --git a/packages/backend/src/remote/activitypub/renderer/add.ts b/packages/backend/src/remote/activitypub/renderer/add.ts
deleted file mode 100644
index 14c71694e1..0000000000
--- a/packages/backend/src/remote/activitypub/renderer/add.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { config } from "@/config.js";
-import type { ILocalUser } from "@/models/entities/user.js";
-
-export default (user: ILocalUser, target: any, object: any) => ({
-	type: "Add",
-	actor: `${config.url}/users/${user.id}`,
-	target,
-	object,
-});
diff --git a/packages/backend/src/remote/activitypub/renderer/remove.ts b/packages/backend/src/remote/activitypub/renderer/remove.ts
deleted file mode 100644
index 270744dd30..0000000000
--- a/packages/backend/src/remote/activitypub/renderer/remove.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { config } from "@/config.js";
-import type { User } from "@/models/entities/user.js";
-
-export default (user: { id: User["id"] }, target: any, object: any) => ({
-	type: "Remove",
-	actor: `${config.url}/users/${user.id}`,
-	target,
-	object,
-});
diff --git a/packages/backend/src/services/i/pin.ts b/packages/backend/src/services/i/pin.ts
index 42fc09b406..574e036fe1 100644
--- a/packages/backend/src/services/i/pin.ts
+++ b/packages/backend/src/services/i/pin.ts
@@ -1,13 +1,10 @@
-import { config } from "@/config.js";
-import renderAdd from "@/remote/activitypub/renderer/add.js";
-import renderRemove from "@/remote/activitypub/renderer/remove.js";
 import { renderActivity } from "@/remote/activitypub/renderer/index.js";
 import { IdentifiableError } from "@/misc/identifiable-error.js";
 import type { User } from "@/models/entities/user.js";
 import type { Note } from "@/models/entities/note.js";
 import { Notes, UserNotePinings, Users } from "@/models/index.js";
 import type { UserNotePining } from "@/models/entities/user-note-pining.js";
-import { genIdAt } from "backend-rs";
+import { genIdAt, renderAdd, renderRemove } from "backend-rs";
 import { deliverToFollowers } from "@/remote/activitypub/deliver-manager.js";
 import { deliverToRelays } from "@/services/relay.js";
 
@@ -115,12 +112,8 @@ export async function deliverPinnedChange(
 
 	if (!Users.isLocalUser(user)) return;
 
-	const target = `${config.url}/users/${user.id}/collections/featured`;
-	const item = `${config.url}/notes/${noteId}`;
 	const content = renderActivity(
-		isAddition
-			? renderAdd(user, target, item)
-			: renderRemove(user, target, item),
+		isAddition ? renderAdd(user.id, noteId) : renderRemove(user.id, noteId),
 	);
 
 	deliverToFollowers(user, content);