Overview
Tracks a set of YouTube playlists you care about (course content, industry talks, competitor channels) and alerts when videos get removed or made private. Essential for educators preserving link equity and researchers tracking platform-moderation events.
Trigger
Cron schedule (every 6 hours)
Schedule
Every 6 hours
Workflow Steps
Load tracked playlists
Read playlist IDs from a config file (typically 5 to 100 playlists).
Scavio YouTube playlist fetch
Pull current video list via Scavio YouTube platform with playlist parameter.
Diff vs snapshot
Compare to the last stored snapshot and identify removed video IDs.
Resolve removed videos
Try fetching each removed video to distinguish deleted vs private vs geo-blocked.
Log takedown events
Persist to Postgres with playlist ID, video ID, removal reason, timestamp.
Slack alert on takedown
Post a notification with video title (from snapshot) and removal reason.
Python Implementation
import os, requests, json
API_KEY = os.environ["SCAVIO_API_KEY"]
H = {"x-api-key": API_KEY}
PLAYLISTS = ["PLxxxxxxxxxxxxxxxxxxxxxx"]
def fetch(playlist_id):
r = requests.post("https://api.scavio.dev/api/v1/search",
headers=H, json={"platform": "youtube", "playlist": playlist_id})
return {v["id"]: v["title"] for v in r.json().get("videos", [])}
prev = json.load(open("snapshot.json")) if os.path.exists("snapshot.json") else {}
cur = {p: fetch(p) for p in PLAYLISTS}
for p, videos in cur.items():
removed = set(prev.get(p, {})) - set(videos)
for vid in removed:
print(f"REMOVED {vid} from {p}: {prev[p][vid]}")
json.dump(cur, open("snapshot.json", "w"))JavaScript Implementation
const API_KEY = process.env.SCAVIO_API_KEY;
const H = { "x-api-key": API_KEY, "content-type": "application/json" };
const PLAYLISTS = ["PLxxxxxxxxxxxxxxxxxxxxxx"];
const fs = require("fs");
async function fetchPl(id) {
const r = await fetch("https://api.scavio.dev/api/v1/search", {
method: "POST", headers: H,
body: JSON.stringify({ platform: "youtube", playlist: id })
}).then(r => r.json());
return Object.fromEntries((r.videos || []).map(v => [v.id, v.title]));
}
const prev = fs.existsSync("snapshot.json") ? JSON.parse(fs.readFileSync("snapshot.json")) : {};
const cur = {};
for (const p of PLAYLISTS) cur[p] = await fetchPl(p);
for (const [p, videos] of Object.entries(cur)) {
const removed = Object.keys(prev[p] || {}).filter(k => !videos[k]);
for (const vid of removed) console.log("REMOVED", vid, prev[p][vid]);
}
fs.writeFileSync("snapshot.json", JSON.stringify(cur));Platforms Used
YouTube
Video search with transcripts and metadata