[object Object]

← back to Marketing Command Center

channels: add /ig-audit — enumerate ALL Instagram accounts (page-linked + business owned/client), not just the 34 page-linked

bec82b0495afaf06b194dd0aeb7280ad2ffeba26 · 2026-07-16 16:53:59 -0700 · Steve Abrams

Files touched

Diff

commit bec82b0495afaf06b194dd0aeb7280ad2ffeba26
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jul 16 16:53:59 2026 -0700

    channels: add /ig-audit — enumerate ALL Instagram accounts (page-linked + business owned/client), not just the 34 page-linked
---
 modules/channels/index.js | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/modules/channels/index.js b/modules/channels/index.js
index 6c5af5f..1bee6f9 100644
--- a/modules/channels/index.js
+++ b/modules/channels/index.js
@@ -554,6 +554,41 @@ module.exports = {
       res.json({ ok: true, saved: Object.keys(updates), status: platformStatus() });
     });
 
+    // Deep IG audit — find EVERY Instagram account this Meta user can reach, not
+    // just the page-linked ones. Three sources, deduped by IG id:
+    //   1. page-linked IG (from /me/accounts → instagram_business_account, per-page)
+    //   2. Business-Manager OWNED igs (/{business}/owned_instagram_accounts)
+    //   3. Business-Manager CLIENT igs (/{business}/client_instagram_accounts)
+    // Personal (non-Business/Creator) IG accounts are invisible to the Graph API and
+    // cannot appear here — that gap is reported so the count is honest.
+    router.get('/ig-audit', async (_req, res) => {
+      const token = env('META_ACCESS_TOKEN');
+      if (!token) return res.status(400).json({ error: 'META_ACCESS_TOKEN not set' });
+      const seen = new Map(); // igId → {username, id, via}
+      const add = (ig, via) => { if (ig && ig.id && !seen.has(ig.id)) seen.set(ig.id, { id: ig.id, username: ig.username || null, via }); };
+      const getJSON = async (u) => { try { const r = await fetch(u); return await r.json(); } catch { return {}; } };
+      try {
+        // 1. page-linked (reuse the fresh per-page resolver)
+        const cache = await fetchMetaPages();
+        for (const p of cache.pages) if (p.igId) add({ id: p.igId, username: p.igUsername }, 'page');
+        // 2 + 3. businesses → owned + client IG accounts
+        const biz = await getJSON(`${GRAPH}/me/businesses?fields=id,name&limit=100&access_token=${encodeURIComponent(token)}`);
+        const businesses = (biz.data || []);
+        for (const b of businesses) {
+          for (const edge of ['owned_instagram_accounts', 'client_instagram_accounts', 'instagram_business_accounts']) {
+            let url = `${GRAPH}/${b.id}/${edge}?fields=username,id&limit=200&access_token=${encodeURIComponent(token)}`, guard = 0;
+            while (url && guard++ < 20) {
+              const j = await getJSON(url);
+              for (const ig of (j.data || [])) add(ig, edge.replace('_instagram_accounts', ''));
+              url = j.paging?.next || null;
+            }
+          }
+        }
+        const all = [...seen.values()].sort((a, b) => (a.username || '').localeCompare(b.username || ''));
+        res.json({ total: all.length, businesses: businesses.map(b => b.name), byVia: all.reduce((m, a) => (m[a.via] = (m[a.via] || 0) + 1, m), {}), accounts: all });
+      } catch (e) { res.status(500).json({ error: e.message }); }
+    });
+
     // OAuth: start the connect flow → redirect the browser to the platform consent.
     router.get('/connect/:platform', (req, res) => {
       const url = authorizeUrl(req.params.platform);

← a8a2166 auto-save: 2026-07-16T16:43:46 (1 files) — package-lock.json  ·  back to Marketing Command Center  ·  docs: IG fleet connect checklist (35 live + convert/link ste bb5f304 →