← 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
M modules/channels/index.js
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 →