Index: src/routes/activitypub.js
===================================================================
--- src/routes/activitypub.js	(revision 4c70ecbe5990b354c76af6b01ef4b3d7e937a63a)
+++ src/routes/activitypub.js	(revision dc4aa08b9b004c449dc53ab40d77b4576e9360f4)
@@ -87,4 +87,24 @@
   ).all(site.id);
   AP.sendAP(res, AP.buildOutbox(baseUrl(req), site, posts));
+});
+
+// ── Blocked collection (owner only, AP §5.6) ──────────────────────
+// The server blocklist is the source of truth for Shaer's "in Orbit":
+// clients read it here instead of keeping their own state. Actor-kind
+// blocks only (domain blocks are instance policy, not an Orbit member).
+router.get('/ap/users/:slug/blocked', (req, res) => {
+  const auth = OAuth.verifyBearer(req.headers.authorization);
+  if (!auth || auth.site.slug !== req.params.slug) return res.status(403).end();
+  const base = baseUrl(req);
+  const items = AP.listBlocks(auth.site.slug)
+    .filter((b) => b.kind === 'actor')
+    .map((b) => b.target);
+  AP.sendAP(res, {
+    '@context': AP.AP_CONTEXT,
+    id: `${base}/ap/users/${auth.site.slug}/blocked`,
+    type: 'OrderedCollection',
+    totalItems: items.length,
+    orderedItems: items,
+  });
 });
 
Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision 4c70ecbe5990b354c76af6b01ef4b3d7e937a63a)
+++ src/services/ActivityPubService.js	(revision dc4aa08b9b004c449dc53ab40d77b4576e9360f4)
@@ -167,4 +167,8 @@
     following: `${id}/following`,
     featured: `${id}/featured`,
+    // AP §5.6: the private blocked collection (owner-only GET). The server
+    // list is the source of truth for Shaer's "in Orbit"; clients keep no
+    // separate state.
+    blocked: `${id}/blocked`,
     // C2S clients (Shaer apps) discover auth + upload here — no hardcoded paths.
     // All four are ActivityPub-spec `endpoints` terms. Dynamic client registration
