Index: CHANGELOG.de.md
===================================================================
--- CHANGELOG.de.md	(revision bf72108b0805b6f9d9780ea0ffef023a60b86f4d)
+++ CHANGELOG.de.md	(revision 4407c67257b1d061f0376ef1ea63ea597c308e4b)
@@ -7,4 +7,10 @@
 
 ### Hinzugefügt
+- **Der Kontoinhaber kann seine eigenen Follower und Gefolgten über C2S lesen.**
+  Die `followers`- und `following`-Sammlungen bleiben für die Öffentlichkeit
+  count-only (Datenschutz), aber eine Anfrage mit einem auf diese Seite
+  begrenzten C2S-Bearer liefert jetzt die echten Actor-URIs, damit eine App
+  (Shaer) eine Freundesliste bauen kann. Für anonyme Aufrufer ändert sich
+  nichts.
 - **App-Zugriff über OAuth 2.0 (ActivityPub Client-to-Server, Phase 1).** Klonkt
   spricht jetzt den standardmäßigen AP-C2S-Auth-Handshake, damit native und
Index: CHANGELOG.md
===================================================================
--- CHANGELOG.md	(revision bf72108b0805b6f9d9780ea0ffef023a60b86f4d)
+++ CHANGELOG.md	(revision 4407c67257b1d061f0376ef1ea63ea597c308e4b)
@@ -7,4 +7,9 @@
 
 ### Added
+- **The account owner can read their own followers and following over C2S.** The
+  `followers` and `following` collections stay count-only for the public
+  (privacy), but a request carrying a C2S bearer scoped to that site now returns
+  the real actor URIs, so an app (Shaer) can build a friends list. Anonymous
+  callers are unchanged.
 - **App access via OAuth 2.0 (ActivityPub Client-to-Server, phase 1).** Klonkt
   now speaks the standard AP C2S auth handshake so native and web clients (the
Index: CHANGELOG.nl.md
===================================================================
--- CHANGELOG.nl.md	(revision bf72108b0805b6f9d9780ea0ffef023a60b86f4d)
+++ CHANGELOG.nl.md	(revision 4407c67257b1d061f0376ef1ea63ea597c308e4b)
@@ -7,4 +7,9 @@
 
 ### Toegevoegd
+- **De account-eigenaar kan zijn eigen followers en following lezen via C2S.** De
+  `followers`- en `following`-collecties blijven count-only voor het publiek
+  (privacy), maar een verzoek met een C2S-bearer die op die site scoped is geeft
+  nu de echte actor-URI's terug, zodat een app (Shaer) een vriendenlijst kan
+  bouwen. Voor anonieme bezoekers verandert er niets.
 - **App-toegang via OAuth 2.0 (ActivityPub Client-to-Server, fase 1).** Klonkt
   spreekt nu de standaard AP C2S-authenticatie, zodat native en web-apps (de
Index: src/routes/activitypub.js
===================================================================
--- src/routes/activitypub.js	(revision bf72108b0805b6f9d9780ea0ffef023a60b86f4d)
+++ src/routes/activitypub.js	(revision 4407c67257b1d061f0376ef1ea63ea597c308e4b)
@@ -84,16 +84,32 @@
 });
 
-// ── Followers (count only) ────────────────────────────────────────
+// ── Followers (count-only public, full for the owner) ─────────────
+// A C2S bearer scoped to this site (the account owner) gets the real actor
+// URIs so their own client can build a friends list; everyone else gets the
+// count only (privacy).
 router.get('/ap/users/:slug/followers', (req, res) => {
-  const site = publicSite(req.params.slug);
-  if (!site) return res.status(404).end();
+  const auth = OAuth.verifyBearer(req.headers.authorization);
+  const owner = auth && auth.site.slug === req.params.slug;
+  const site = owner ? auth.site : publicSite(req.params.slug);
+  if (!site) return res.status(404).end();
+  if (owner) {
+    const items = db.prepare('SELECT actor_uri FROM ap_followers WHERE slug = ? ORDER BY created_at').all(site.slug).map((r) => r.actor_uri);
+    return AP.sendAP(res, AP.buildFollowers(baseUrl(req), site, items.length, items));
+  }
   const n = db.prepare('SELECT COUNT(*) n FROM ap_followers WHERE slug = ?').get(site.slug).n;
   AP.sendAP(res, AP.buildFollowers(baseUrl(req), site, n));
 });
 
-// ── Following (count only) ────────────────────────────────────────
+// ── Following (count-only public, full for the owner) ─────────────
 router.get('/ap/users/:slug/following', (req, res) => {
-  const site = publicSite(req.params.slug);
-  if (!site) return res.status(404).end();
+  const auth = OAuth.verifyBearer(req.headers.authorization);
+  const owner = auth && auth.site.slug === req.params.slug;
+  const site = owner ? auth.site : publicSite(req.params.slug);
+  if (!site) return res.status(404).end();
+  if (owner) {
+    let items = [];
+    try { items = db.prepare("SELECT actor_uri FROM ap_following WHERE slug = ? AND status = 'accepted' ORDER BY created_at").all(site.slug).map((r) => r.actor_uri); } catch { /* table may not exist */ }
+    return AP.sendAP(res, AP.buildFollowing(baseUrl(req), site, items.length, items));
+  }
   let n = 0;
   try { n = db.prepare("SELECT COUNT(*) n FROM ap_following WHERE slug = ? AND status = 'accepted'").get(site.slug).n; } catch { /* table may not exist */ }
Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision bf72108b0805b6f9d9780ea0ffef023a60b86f4d)
+++ src/services/ActivityPubService.js	(revision 4407c67257b1d061f0376ef1ea63ea597c308e4b)
@@ -542,5 +542,8 @@
 }
 
-export function buildFollowers(base, site, count) {
+// Public callers get a count-only collection (privacy). The authenticated
+// account owner (a C2S bearer scoped to this site) gets the real actor URIs via
+// `items`, so their own client can build a friends list.
+export function buildFollowers(base, site, count, items = null) {
   const id = `${actorId(base, site.slug)}/followers`;
   return {
@@ -548,6 +551,6 @@
     id,
     type: 'OrderedCollection',
-    totalItems: count || 0,
-    orderedItems: [], // hidden for privacy; count only
+    totalItems: items ? items.length : (count || 0),
+    orderedItems: items || [], // count-only for the public; full for the owner
   };
 }
@@ -555,5 +558,5 @@
 // The accounts this site follows — count only, mirroring buildFollowers. The spec lists
 // `following` as a standard actor property; Hubzilla/Friendica + crawlers expect it.
-export function buildFollowing(base, site, count) {
+export function buildFollowing(base, site, count, items = null) {
   const id = `${actorId(base, site.slug)}/following`;
   return {
@@ -561,6 +564,6 @@
     id,
     type: 'OrderedCollection',
-    totalItems: count || 0,
-    orderedItems: [], // count only
+    totalItems: items ? items.length : (count || 0),
+    orderedItems: items || [], // count-only for the public; full for the owner
   };
 }
Index: test/c2s-collections.test.js
===================================================================
--- test/c2s-collections.test.js	(revision 4407c67257b1d061f0376ef1ea63ea597c308e4b)
+++ test/c2s-collections.test.js	(revision 4407c67257b1d061f0376ef1ea63ea597c308e4b)
@@ -0,0 +1,44 @@
+// C2S owner collections: followers/following are count-only by default, but
+// carry the real actor URIs when the account owner asks (klonkt-demo-6kc).
+// The route-level bearer gate is verified live; here we cover the builders.
+
+import { test } from 'node:test';
+import assert from 'node:assert/strict';
+
+process.env.DATABASE_PATH = ':memory:';
+process.env.PUBLIC_BASE_URL = 'https://klonkt.test';
+
+const dbMod = await import('../src/config/database.js');
+dbMod.initializeDatabase();
+const AP = await import('../src/services/ActivityPubService.js');
+
+const base = 'https://klonkt.test';
+const site = { slug: 'me', primary_slug: 'me' };
+
+test('followers/following are count-only when no items are passed', () => {
+  const f = AP.buildFollowers(base, site, 7);
+  assert.equal(f.type, 'OrderedCollection');
+  assert.equal(f.totalItems, 7);
+  assert.deepEqual(f.orderedItems, []);
+
+  const g = AP.buildFollowing(base, site, 3);
+  assert.equal(g.totalItems, 3);
+  assert.deepEqual(g.orderedItems, []);
+});
+
+test('the owner view carries the real actor URIs', () => {
+  const uris = ['https://a.test/actor', 'https://b.test/actor'];
+  const f = AP.buildFollowers(base, site, 999, uris);
+  assert.deepEqual(f.orderedItems, uris);
+  assert.equal(f.totalItems, 2); // reflects the items, not the passed count
+
+  const g = AP.buildFollowing(base, site, 0, uris);
+  assert.deepEqual(g.orderedItems, uris);
+  assert.equal(g.totalItems, 2);
+});
+
+test('an empty owner list is a valid empty collection, not count-only fallback', () => {
+  const f = AP.buildFollowers(base, site, 5, []);
+  assert.equal(f.totalItems, 0);
+  assert.deepEqual(f.orderedItems, []);
+});
