Index: src/routes/activitypub.js
===================================================================
--- src/routes/activitypub.js	(revision 72d8effdf53546d3adac9fdbd366a46a551be787)
+++ src/routes/activitypub.js	(revision a66266d23ff7d4cf2f10b82f0aaefa77102e6848)
@@ -630,4 +630,28 @@
 });
 
+// ── Playlist als dereferenceerbare AP-collectie (shaer-ayc) ───────
+// De eerste stap van het Funkwhale-spoor: een playlist heeft een id, dus een
+// stabiele URI. Alleen het fedi_open-deel staat erin (de poort is per bestand
+// en eenrichtings; zie setAudioFediOpen in routes/posts.js) — een collectie
+// zonder open tracks bestaat wel maar is leeg, want de playlist zelf is niet
+// geheim, alleen de bestanden erachter.
+// De lijst van alle playlist-collecties (shaer-ayc, stap 2). De actor wijst
+// hierheen via AS2 `streams`. Kaal standaard; verrijkte stubs op verzoek
+// (FEP-9876), dezelfde conventie als followers/following.
+router.get('/ap/users/:slug/playlists', (req, res) => {
+  const site = publicSite(req.params.slug);
+  if (!site) return res.status(404).end();
+  AP.sendAP(res, AP.listPlaylistsAP(baseUrl(req), site, wantsEnriched(req, res)));
+});
+
+router.get('/ap/users/:slug/playlists/:id', (req, res) => {
+  const site = publicSite(req.params.slug);
+  if (!site) return res.status(404).end();
+  const pl = db.prepare('SELECT id, title, artist, year, cover_url, kind FROM playlists WHERE id = ? AND site_id = ?')
+    .get(req.params.id, site.id);
+  if (!pl) return res.status(404).end();
+  AP.sendAP(res, AP.buildPlaylistCollection(baseUrl(req), site, pl, AP.playlistOpenTracks(pl.id)));
+});
+
 // ── Note ──────────────────────────────────────────────────────────
 router.get('/ap/notes/:id', async (req, res) => {
