Index: src/routes/activitypub.js
===================================================================
--- src/routes/activitypub.js	(revision 4c70ecbe5990b354c76af6b01ef4b3d7e937a63a)
+++ src/routes/activitypub.js	(revision 8b07c12ebf1ce49b35e4b1ca78fddc8702616a5b)
@@ -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 8b07c12ebf1ce49b35e4b1ca78fddc8702616a5b)
@@ -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
Index: test/activitypub-as2.test.js
===================================================================
--- test/activitypub-as2.test.js	(revision 4c70ecbe5990b354c76af6b01ef4b3d7e937a63a)
+++ test/activitypub-as2.test.js	(revision 8b07c12ebf1ce49b35e4b1ca78fddc8702616a5b)
@@ -30,4 +30,6 @@
   'totalItems', 'orderedItems', 'items', 'first', 'last', 'partOf', 'next', 'prev',
   'preferredUsername', 'inbox', 'outbox', 'followers', 'following', 'endpoints', 'sharedInbox',
+  // ActivityPub §5.6: the private blocked collection (owner-only GET).
+  'blocked',
   // ActivityPub §4.1 `endpoints` vocabulary (same category as sharedInbox), used for C2S.
   'oauthAuthorizationEndpoint', 'oauthTokenEndpoint', 'uploadMedia',
Index: test/c2s-block.test.js
===================================================================
--- test/c2s-block.test.js	(revision 4c70ecbe5990b354c76af6b01ef4b3d7e937a63a)
+++ test/c2s-block.test.js	(revision 8b07c12ebf1ce49b35e4b1ca78fddc8702616a5b)
@@ -43,2 +43,15 @@
   assert.equal(out.status, 400);
 });
+
+test('the actor advertises the blocked collection (AP 5.6)', () => {
+  site.primary_slug = 'me';
+  const actor = AP.buildActor('https://test.example', site);
+  assert.equal(actor.blocked, 'https://test.example/ap/users/me/blocked');
+});
+
+test('actor-kind blocks form the collection items; domain blocks stay out', async () => {
+  await AP.ingestOutboxActivity(site, user, { type: 'Block', object: BULLY });
+  await AP.blockTarget(site, 'nare-server.example');   // domain block
+  const items = AP.listBlocks('me').filter((b) => b.kind === 'actor').map((b) => b.target);
+  assert.deepEqual(items, [BULLY]);
+});
