[object Object]

← back to Marketing Command Center

youtube: add /youtube/visibility endpoint (flip private↔public via videos.update)

e10410d716d266c6ad89b106c2cf6f9fca0e6235 · 2026-07-21 14:09:37 -0700 · Steve

Files touched

Diff

commit e10410d716d266c6ad89b106c2cf6f9fca0e6235
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Jul 21 14:09:37 2026 -0700

    youtube: add /youtube/visibility endpoint (flip private↔public via videos.update)
---
 modules/channels/index.js | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/modules/channels/index.js b/modules/channels/index.js
index c9deb80..2436be8 100644
--- a/modules/channels/index.js
+++ b/modules/channels/index.js
@@ -816,6 +816,26 @@ module.exports = {
         res.json({ ok: true, live, results, note: live ? 'Live posts fired to connected channels; unconnected staged.' : 'Staged only — connect channels + confirm with dryRun:false to post live.' });
       } catch (e) { res.status(500).json({ error: e.message }); }
     });
+
+    // Flip a YouTube video's visibility (private ↔ unlisted ↔ public) via videos.update.
+    // Lets us post Private, review, then publish Public — customer-facing, so gated.
+    router.post('/youtube/visibility', async (req, res) => {
+      try {
+        const { videoId, privacy = 'public' } = req.body || {};
+        if (!videoId) return res.status(400).json({ error: 'videoId required' });
+        if (!['private', 'unlisted', 'public'].includes(privacy)) return res.status(400).json({ error: 'privacy must be private|unlisted|public' });
+        const token = await youtubeAccessToken();
+        if (!token) return res.status(400).json({ error: 'YouTube not connected' });
+        const r = await fetch('https://www.googleapis.com/youtube/v3/videos?part=status', {
+          method: 'PUT',
+          headers: { authorization: `Bearer ${token}`, 'content-type': 'application/json' },
+          body: JSON.stringify({ id: videoId, status: { privacyStatus: privacy } }),
+        });
+        const j = await r.json().catch(() => ({}));
+        if (!r.ok || j.error) return res.status(500).json({ error: JSON.stringify(j.error || {}).slice(0, 200) });
+        res.json({ ok: true, id: videoId, privacy: j.status?.privacyStatus, url: `https://youtu.be/${videoId}` });
+      } catch (e) { res.status(500).json({ error: e.message }); }
+    });
   },
 };
 

← 85d08d7 youtube: build resumable videos.insert upload pipeline (post  ·  back to Marketing Command Center  ·  youtube: add force-ssl scope for videos.update (visibility c 70479a4 →