Index: src/routes/activitypub.js
===================================================================
--- src/routes/activitypub.js	(revision abb34d28fa7b00ac177efcae60239dd20f8f5eb1)
+++ src/routes/activitypub.js	(revision 75bda38ace3a3eff7e9ea9d7676a2ad33a8ad24c)
@@ -6,4 +6,5 @@
  *   GET /ap/users/:slug/outbox     OrderedCollection of Create(Note)
  *   GET /ap/users/:slug/followers  count-only OrderedCollection
+ *   GET /ap/users/:slug/featured   pinned posts (Mastodon "Featured" tab)
  *   GET /ap/notes/:id              a single Note
  *   POST /ap/users/:slug/inbox, /ap/inbox  → 202 (Follow/Accept + signature verify: next step)
@@ -72,4 +73,17 @@
 });
 
+// ── Featured (pinned posts → Mastodon "Featured" tab) ─────────────
+router.get('/ap/users/:slug/featured', (req, res) => {
+  const site = publicSite(req.params.slug);
+  if (!site) return res.status(404).end();
+  const posts = db.prepare(
+    `SELECT id, slug, title, content, cover_image_url, published_at, created_at
+     FROM posts WHERE site_id = ? AND status = 'published' AND (fan_only IS NULL OR fan_only = 0)
+       AND pinned IS NOT NULL AND pinned > 0
+     ORDER BY pinned ASC, COALESCE(published_at, created_at) DESC LIMIT 20`
+  ).all(site.id);
+  AP.sendAP(res, AP.buildFeatured(baseUrl(req), site, posts));
+});
+
 // ── Note ──────────────────────────────────────────────────────────
 router.get('/ap/notes/:id', (req, res) => {
Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision abb34d28fa7b00ac177efcae60239dd20f8f5eb1)
+++ src/services/ActivityPubService.js	(revision 75bda38ace3a3eff7e9ea9d7676a2ad33a8ad24c)
@@ -83,4 +83,5 @@
     outbox: `${id}/outbox`,
     followers: `${id}/followers`,
+    featured: `${id}/featured`,
     endpoints: { sharedInbox: `${base}/ap/inbox` },
     publicKey: {
@@ -240,4 +241,19 @@
     totalItems: count || 0,
     orderedItems: [], // hidden for privacy; count only
+  };
+}
+
+// Pinned posts → the actor's `featured` collection. Mastodon reads this and shows
+// these as the "Featured" tab (pinned to the profile). Posts come ordered by pin
+// rank; embedded as full Notes so a remote server doesn't need extra fetches.
+export function buildFeatured(base, site, posts) {
+  const id = `${actorId(base, site.slug)}/featured`;
+  const items = (posts || []).map((p) => buildNote(base, site, p));
+  return {
+    '@context': 'https://www.w3.org/ns/activitystreams',
+    id,
+    type: 'OrderedCollection',
+    totalItems: items.length,
+    orderedItems: items,
   };
 }
@@ -968,5 +984,5 @@
 export default {
   getOrCreateKeys, apWants, sendAP, actorId, noteId,
-  buildActor, buildNote, buildCreate, buildOutbox, buildFollowers,
+  buildActor, buildNote, buildCreate, buildOutbox, buildFollowers, buildFeatured,
   followerCount, deliver, fetchActor, verifyRequest, handleInbox, deliverCreate, deliverDelete, deliverUpdate,
   getInteractions, getInteractionById, buildReplyNote, getOutboxNote, deliverReply, resolveRemoteNote,
