YouTube Channel Scraper & Monitor - New Videos, Live Alerts avatar

YouTube Channel Scraper & Monitor - New Videos, Live Alerts

Pricing

$1.30 / 1,000 results

Go to Apify Store
YouTube Channel Scraper & Monitor - New Videos, Live Alerts

YouTube Channel Scraper & Monitor - New Videos, Live Alerts

YouTube channel monitor: scrape channels by URL or @handle for subscribers, YouTube new videos, uploads, Shorts and live streams. YouTube alerts for new uploads to Discord, Telegram, Slack or webhook. No login, no proxies, no API key. $0.0013 per row.

Pricing

$1.30 / 1,000 results

Rating

0.0

(0)

Developer

Luke Hunter

Luke Hunter

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

4 days ago

Last modified

Share

A YouTube channel monitor: YouTube new videos, uploads, Shorts and live streams for any list of channels - plus discover similar channels for influencer research. Give it channel URLs or @handles and it returns one row per channel - subscribers and video count - plus one row per item on the tabs you pick: the Videos tab (title, views, age, length), the Shorts tab (title, views) and the Live tab of past streams (title, views, age, length), each read from the channel's public page. Add the Featured channels tab and each creator's channel page also yields the channels they feature - a ready list of related creators with subscriber counts - and discoverDepth: 1 reads those channels too, so three seed channels become a creator list in one run. Turn on change mode (onlyChangedSinceLastRun), put it on a schedule, and every run delivers only what is new since the last run - new videos, new Shorts and new streams, each tracked on its own - so you can route fresh uploads from the channels you follow, compete with or sponsor straight to Slack, Google Sheets, email or your own pipeline. Built for creators, agencies, influencer-marketing and brand teams, newsrooms, researchers and analysts.

Pay-per-result: $0.0013 per row - 1,000 rows = $1.30. No start fee, no login, no proxies, no API key, and in change mode nothing unchanged is charged.

It is deliberately narrow: channel pages only, and what a tab shows on its first page - about 30 newest videos, 48 newest Shorts, a channel's past streams and the handful of channels it features (usually 3 to 13). It does not open video pages, search YouTube, or read comments or transcripts (see Limits for exactly why).

Quick start (1 minute)

  1. Open the Input tab and paste YouTube channel URLs (https://www.youtube.com/@mkbhd) or bare @handles.
  2. Choose the Tabs to read: Videos (the default), Shorts, Live streams, Featured channels - any combination.
  3. Click Start, then export the dataset to CSV/Excel/JSON or pull it through the API shown below.
{
"channelUrls": [
"https://www.youtube.com/@mkbhd",
"@veritasium"
],
"tabs": ["videos", "shorts", "streams"],
"output": "channelsAndVideos",
"maxItems": 300
}

Leave tabs out and you get the Videos tab only, exactly as before this option existed. Every common channel link form works: /@handle, /channel/UC..., /c/<name>, /user/<name>, a bare @handle, and a link with a tab such as /videos or /shorts (the link's own tab is ignored - the tabs input decides). Video links are not accepted (watch, youtu.be, shorts): the run stops at once with a clear message, because YouTube asks automated clients to sign in on video pages and this Actor never bypasses that. Paste the channel URL instead.

What one channel costs per tab. Every channel gives one channel row, once, however many tabs you pick; each tab adds a row per item it shows. At $0.0013 per row:

Tabs read for one channelRowsCost
Videos (the default): channel row + up to 30 videosabout 31about $0.0403
Shorts: channel row + up to 48 Shortsabout 49about $0.0637
Live: channel row + its past streams (@mkbhd shows 5, @TED shows 11)about 6-12about $0.0078-$0.0156
Videos + Shorts + Live: one channel row + all three tabs (@mkbhd: 30 + 48 + 5)about 84about $0.1092
Featured channels: channel row + the channels it features (@mkbhd 6, @veritasium 12, @TED 3)about 4-13about $0.0052-$0.0169
output: "channelsOnly": just the channel row1$0.0013

The default maxItems of 100 covers about three channels on Videos alone. Raise maxItems for longer lists or more tabs - it is your cost cap.

YouTube channel monitor: what you can use it for

  • New-video alerts for YouTube channels. Follow competitors, partners, news sources or creators you sponsor: schedule the Actor in change mode and get a row for each new upload, with its title, link and length, as soon as a run sees it.
  • YouTube channel monitoring. Track subscriberCountText and videoCountText for a watch list of channels over time; in change mode a channel row arrives when either displayed figure moves.
  • Creator and competitor research. Put 50 channels in one run and get each channel's subscribers, video count and latest uploads with rounded views, ages and lengths, ready for a spreadsheet.
  • Discover similar channels for influencer research. Add the Featured channels tab to see which creators a channel features, with their subscriber counts; set discoverDepth to 1 and those channels are read too, giving you a creator list to qualify for sponsorships, collaborations or outreach lists. See the recipe below.

Example output

Videos, Shorts, streams and featured channels each have their own row type: video, short, stream and featuredChannel. The first two video rows from a real page (youtube.com/@mkbhd/videos):

{
"type": "video",
"channelId": "UCBJycsmduvYEL83R_U4JriQ",
"channelName": "Marques Brownlee",
"channelHandle": "@mkbhd",
"videoId": "3iRUwVzRDZQ",
"url": "https://www.youtube.com/watch?v=3iRUwVzRDZQ",
"title": "Xiaomi 18 Fold: How Does This Happen?",
"viewCountText": "6.1 million views",
"viewCount": 6100000,
"publishedText": "1 day ago",
"durationText": "10:47",
"durationSeconds": 647,
"position": 1,
"isNew": null,
"scrapedAt": "2026-10-04T08:00:00.000Z"
}
{
"type": "video",
"channelId": "UCBJycsmduvYEL83R_U4JriQ",
"channelName": "Marques Brownlee",
"channelHandle": "@mkbhd",
"videoId": "e1q-TuHdc4Y",
"url": "https://www.youtube.com/watch?v=e1q-TuHdc4Y",
"title": "Dear YouTube!",
"viewCountText": "5.2 million views",
"viewCount": 5200000,
"publishedText": "3 days ago",
"durationText": "12:23",
"durationSeconds": 743,
"position": 2,
"isNew": null,
"scrapedAt": "2026-10-04T08:00:00.000Z"
}

position 1 is the newest video on the channel. isNew is null in a normal run and on the first run of change mode (nothing is known yet) and true for a video delivered because it was not seen before.

A Short row (tabs includes shorts; first row of youtube.com/@mkbhd/shorts). The Shorts tab shows a title and a view count and nothing else, so publishedText, durationText and durationSeconds are always null - never guessed - and the link is a /shorts/ address for you to open:

{
"type": "short",
"channelId": "UCBJycsmduvYEL83R_U4JriQ",
"channelName": "Marques Brownlee",
"channelHandle": "@mkbhd",
"videoId": "R6yNUnRXZ64",
"url": "https://www.youtube.com/shorts/R6yNUnRXZ64",
"title": "iPhone Duo is here",
"viewCountText": "7.4M views",
"viewCount": 7400000,
"publishedText": null,
"durationText": null,
"durationSeconds": null,
"position": 1,
"isNew": null,
"scrapedAt": "2026-10-04T08:00:00.000Z"
}

A stream row (tabs includes streams; first row of youtube.com/@mkbhd/streams): the video fields plus liveStatus, which is "past" for a finished stream (the Live tab words it "Streamed 8 years ago") and null whenever the page does not say so:

{
"type": "stream",
"channelId": "UCBJycsmduvYEL83R_U4JriQ",
"channelName": "Marques Brownlee",
"channelHandle": "@mkbhd",
"videoId": "49-rK7SAfQk",
"url": "https://www.youtube.com/watch?v=49-rK7SAfQk",
"title": "5 New Phone Updates + Giveaway Update!",
"viewCountText": "944 thousand views",
"viewCount": 944000,
"publishedText": "Streamed 8 years ago",
"durationText": "41:41",
"durationSeconds": 2501,
"liveStatus": "past",
"position": 1,
"isNew": null,
"scrapedAt": "2026-10-04T08:00:00.000Z"
}

A featuredChannel row (tabs includes channels; the first of the 6 channels @mkbhd features, from a real page). It says which channel features it (sourceChannelId, sourceHandle); channelId and url are the featured channel's own, ready to feed back into channelUrls:

{
"type": "featuredChannel",
"sourceChannelId": "UCBJycsmduvYEL83R_U4JriQ",
"sourceHandle": "@mkbhd",
"channelId": "UCG7J20LhUeLl6y_Emi7OJrA",
"handle": "@TheStudio",
"name": "The Studio",
"url": "https://www.youtube.com/channel/UCG7J20LhUeLl6y_Emi7OJrA",
"subscriberCountText": "1.16M subscribers",
"subscriberCount": 1160000,
"videoCountText": "219 videos",
"position": 1,
"isNew": null,
"scrapedAt": "2026-10-04T08:00:00.000Z"
}

The channel row from the same pages (description shortened here for space):

{
"type": "channel",
"channelId": "UCBJycsmduvYEL83R_U4JriQ",
"url": "https://www.youtube.com/channel/UCBJycsmduvYEL83R_U4JriQ",
"handle": "@mkbhd",
"name": "Marques Brownlee",
"subscriberCountText": "21.3M subscribers",
"subscriberCount": 21300000,
"videoCountText": "1.8K videos",
"videoCount": 1800,
"description": "MKBHD: Quality Tech Videos | YouTuber | Geek | Consumer Electronics | Tech Head | Internet Personality! ...",
"avatarUrl": "https://yt3.googleusercontent.com/qu4TmIaYUlS41-dJ9gZ7DUR3nilvmB5_11i6OKSdvNnBNiyOusZP1bMN6ICnuxtjFBb6ioKgRQ=s900-c-k-c0x00ffffff-no-rj",
"status": "ok",
"statusReason": null,
"changedFields": null,
"newVideoIds": null,
"scrapedAt": "2026-10-04T08:00:00.000Z"
}

Numbers are shown the way YouTube shows them: rounded. A channel page says "21.3M subscribers", "1.8K videos" and "6.1 million views", so subscriberCount, videoCount and viewCount are whole numbers parsed from that text and are approximate. The text exactly as YouTube shows it is always kept next to them (subscriberCountText, videoCountText, viewCountText). publishedText is a relative age ("3 days ago"), not a date.

A channel that answers 404 is delivered once as a single unavailable channel row with everything the page would have given set to null - no tab rows, whichever tabs you asked for.

Recipe: build a creator list for influencer research

Seed three channels in your niche and let the Actor add the channels they feature. One run, no search, no login:

{
"channelUrls": [
"https://www.youtube.com/@mkbhd",
"@veritasium",
"https://www.youtube.com/@LinusTechTips"
],
"tabs": ["channels"],
"discoverDepth": 1,
"maxDiscoveredChannels": 20,
"maxItems": 100
}

What you get, in this order:

  1. For each seed: its channel row (subscribers, video count, description, avatar) and one featuredChannel row per channel it features - on the real pages @mkbhd features 6, @veritasium 12 and @LinusTechTips 12.
  2. Then one channel row for each channel found (de-duplicated by channelId, up to maxDiscoveredChannels, 20 by default), marked discoveredFrom with the channelId of the seed that featured it. Filter on subscriberCount and videoCount to qualify them, and sort by discoveredFrom to see which seed each came from.

Add "videos" to tabs and each discovered channel also delivers its latest videos (the same tabs as the seeds, except channels): a creator list with recent output in one dataset. Discovery goes one hop only: the channels a discovered channel features are never read, so the run cannot snowball. discoverDepth: 1 needs "channels" in tabs; maxItems still caps every row, and pacing and robots.txt rules are the same as for any channel.

Cost example (at $0.0013 per row): the input above is 3 seed channel rows + 30 featured-channel rows + 20 discovered channel rows = 53 rows, about $0.0689. Every one of those rows is a normal delivered result; set maxDiscoveredChannels lower, or maxItems, to spend less. With "tabs": ["videos", "channels"] each discovered channel adds up to 31 rows (its channel row and 30 videos, about $0.04), so keep maxDiscoveredChannels small or maxItems tight there.

Run it again later with onlyChangedSinceLastRun: true: only newly featured channels (isNew: true, with the source that now features them) and channels whose displayed counts changed are delivered.

Alert on new uploads: schedule + Slack, Sheets or webhook

Change mode makes this a new-upload feed. The first run delivers everything (the channel row with changedFields: ["firstSeen"] and the items of every tab you selected, isNew: null) and remembers it. Every run after that delivers:

  • a video, short, stream or featuredChannel row (isNew: true) for each item not seen before on that channel and tab, newest first (a featuredChannel row means a channel newly featured by that source channel; each source channel has its own list) - nothing else about an item can trigger a row, so rounded view-count growth costs nothing;
  • a channel row only when its displayed subscriber count, video count or status changed (changedFields says which; newVideoIds lists any new uploads, Shorts and streams seen in the same run).

Each tab is tracked on its own. Add "shorts", "streams" or "channels" to a watch that already exists and that tab gets a quiet baseline: its items are delivered once, on that run, as the first run for that tab (isNew: null), and remembered - the following runs report only genuinely new Shorts, streams or featured channels, never the 48 you just saw as "new". State written before the tabs option existed keeps working unchanged.

  1. Save this as a Task (your watch list, with change mode on):
{
"channelUrls": [
"https://www.youtube.com/@mkbhd",
"@veritasium",
"https://www.youtube.com/@LinusTechTips"
],
"tabs": ["videos", "shorts"],
"output": "channelsAndVideos",
"onlyChangedSinceLastRun": true,
"maxItems": 500
}
  1. Add a Schedule (hourly to catch uploads quickly, daily for a digest).
  2. In the Task's Integrations tab add one or more of:
    • Slack (or email) on "Run succeeded", sending the dataset - every run that finds new uploads posts them; a run with nothing new has an empty dataset, so there is nothing to post;
    • Google Sheets: append each run's dataset rows to a sheet, a running log of every new upload;
    • a Webhook for ACTOR.RUN.SUCCEEDED (or Make, Zapier, n8n) that fetches the run's dataset and acts on rows where type is video, short or stream and isNew is true.
  3. Add channels to the Task whenever you like. A channel added later gets its own baseline run (everything delivered once); the others stay quiet until they post.

A run where nothing is new succeeds with an empty dataset and charges nothing; the run's status message says so, e.g. "Checked 3 of 3 URL(s). 3 unchanged. Nothing to deliver or charge."

Steady-state cost is tiny: $0.0013 per new upload, Short or stream. A channel row only appears when YouTube's rounded subscriber or video figure changes (for example 21.3M to 21.4M), which for a large channel is rare. With output: "channelsOnly" change mode returns just the channel row, and also delivers it when a new upload appears (changedFields includes newVideos, newVideoIds lists the videos), so it still works as an upload alert at one row per event.

State is kept in a named key-value store (stateStoreName, default youtube-stats-tracker-state), so it survives between runs. Use another name to keep separate histories for separate tasks, or delete the store to start over with a fresh baseline.

Get new YouTube video alerts in Discord, Telegram, Slack, Teams, Google Chat, your phone (ntfy/Pushover) or any app (webhook)

Want a new YouTube video alert in Discord, Telegram, Slack or on your phone instead of reading rows from a dataset? Turn on change mode (onlyChangedSinceLastRun), add one or more destinations in the Actor's Notifications (optional) section, and each run sends the new uploads it delivered, with the title, link, views, length and thumbnail. It is a monitor that notifies you: it only reads public channel pages, and it never subscribes, comments, likes or logs in to YouTube.

ChannelFill inWhat you get
DiscorddiscordWebhookUrlOne card per update, 10 per message
TelegramtelegramBotToken + telegramChatIdUp to 10 updates per message, one line each
SlackslackWebhookUrlBlock Kit message, up to 10 updates per message
Mattermost, Rocket.ChatslackCompatibleWebhookUrlSlack-style incoming webhook: a Markdown list (these apps do not render Slack Block Kit)
Microsoft TeamsteamsWebhookUrlAdaptive Card from a Teams Workflows webhook, up to 10 updates per card
Google ChatgoogleChatWebhookUrlCard in a space, up to 10 updates per message
ntfy (phone push, no account)ntfyTopic (optional ntfyServer, ntfyToken)A push per update, up to 10, then one "...and N more"
Pushover (phone push)pushoverUserKey + pushoverAppTokenA push per update, up to 10, then one "...and N more"
Any app: Zapier, Make, n8n, Pipedream, IFTTT, Home AssistantwebhookUrlOne JSON POST per run with every delivered row

Fill in as many as you like; each channel works on its own and a failing one never stops the others.

Setup, channel by channel

  • Discord (1 minute): Server Settings → Integrations → Webhooks → New Webhook → pick the channel → Copy Webhook URL.
  • Telegram (2 minutes): chat with @BotFather, send /newbot, paste the token into telegramBotToken. Add the bot to the chat or channel (administrator for a channel; for a chat with yourself press Start on the bot). telegramChatId is a numeric id (123456789, or -1001234567890 for a group or channel) or a public @channelusername.
  • Slack: at api.slack.com/apps create an app (From scratch) → Incoming Webhooks → switch it on → Add New Webhook to Workspace → pick the channel → copy the https://hooks.slack.com/services/... URL.
  • Mattermost or Rocket.Chat: create an incoming webhook in the app and paste its public https:// URL into slackCompatibleWebhookUrl. It must be reachable from the internet: localhost, private IP addresses and .local / .internal names are rejected.
  • Microsoft Teams: in the channel open ... → Workflows → Post to a channel when a webhook request is received → finish the wizard and copy the URL (a host ending in .logic.azure.com or .powerplatform.com). The old "Office 365 connector" webhooks are retired and not supported.
  • Google Chat: in the space open Apps & integrations → Webhooks → Add webhook, name it and copy the https://chat.googleapis.com/v1/spaces/... URL (needs a Google Workspace account that allows webhooks).
  • ntfy (free, no account): install the ntfy app (or open ntfy.sh), subscribe to a topic with a long random name, and put the same name in ntfyTopic. Anyone who knows the topic can read it, so make it unguessable. Own server or a protected topic: set ntfyServer (public https:// address) and ntfyToken.
  • Pushover: copy your user key from pushover.net into pushoverUserKey, create an application at pushover.net/apps/build and put its API token in pushoverAppToken. Pushover itself is a separate paid app after its trial: check their pricing.
  • Any app (webhook): paste the catch-hook URL from Zapier, Make, n8n, Pipedream, IFTTT or Home Assistant into webhookUrl. It must be a public https:// address.

The recipe: a new-upload alert for the channels you follow, in five steps

  1. Put the channels in channelUrls, and add shorts and streams to tabs if you want those too.
  2. Turn on onlyChangedSinceLastRun.
  3. Add your destination(s) from the table above.
  4. Run it once. The first run is the baseline: it records what is already there and sends nothing, so you are not flooded with old videos.
  5. Create a Schedule (hourly to catch uploads quickly, daily for a digest). From then on each run sends only what is new.
{
"channelUrls": [
"https://www.youtube.com/@mkbhd",
"@veritasium"
],
"tabs": [
"videos",
"shorts"
],
"output": "channelsAndVideos",
"onlyChangedSinceLastRun": true,
"maxItems": 500,
"discordWebhookUrl": "https://discord.com/api/webhooks/<id>/<token>",
"ntfyTopic": "your-long-random-topic-name",
"maxNotifications": 25
}

The generic webhook (webhookUrl)

Each run sends one POST per 100 delivered rows (so usually one) with this body, followed by whatever your tool does with it:

{
"source": "apify",
"actor": "youtube-stats-tracker",
"runId": "HG7ML7M8z78YcAPEB",
"count": 2,
"total": 2,
"part": 1,
"parts": 1,
"items": [
{
"...": "the full dataset rows"
}
]
}

count is the number of rows in this POST, total the number delivered in the run, and part / parts number the POSTs when a run delivers more than 100 rows. maxNotifications does not cap the webhook: your tool gets every delivered row. Your endpoint should answer with a 2xx status; redirects are not followed and a failure is reported in the run status, never retried in a loop.

What each message contains

In change mode these delivered rows are announced, uploads first:

  • A new video, Short or stream (isNew: true): New video: <title> — <channel>, New Short: ... or New stream: ..., with the link, views, age and length that YouTube shows (a Short shows neither age nor length, so they are left out), the channel's @handle, and the video's thumbnail. The thumbnail is YouTube's standard public still for the video id; the dataset has no thumbnail field and this Actor never requests the image, only the chat service showing it does.
  • A channel change (a channel row whose displayed figures moved): Subscribers: 1.2M → 1.3M — <channel>, Videos: 1.9K → 2K — <channel>, Channel unavailable or Channel is back, with the channel's picture. These figures are YouTube's rounded displays, so a small channel changes often and a large one rarely. With output: "channelsOnly" the channel row is also the upload signal, so a new upload is announced as New upload: <channel> (1 new) linking to the channel (use channelsAndVideos to get a link to each video).
  • A newly featured channel (featuredChannel, isNew: true, only when tabs includes channels): New featured channel: <name> — featured by <source>, with its subscribers and video count.

Never announced: the baseline run (the first run for a watch, or for a newly added channel or tab: those rows have isNew: null or firstSeen), rounded view-count growth (not a change at all), and any run that is not in change mode. Discord gets a card per item, Telegram one line per item, Slack Block Kit sections, Teams an Adaptive Card, Google Chat a card, and ntfy/Pushover a push that opens the video or channel. Link previews are off, and titles are escaped so a title can never inject formatting or mentions.

What to expect

  • Only delivered rows are sent, and only in change mode. A normal run delivers every row rather than only what is new, so it sends nothing (the status message says so). A run with nothing new sends nothing, so there is no "no new videos" spam. Rows the run could not deliver (for example because your spending limit was reached) are not sent.
  • maxNotifications (default 25, max 100) caps how many updates go to chat and phone channels per run (the webhook is not capped). The rest are still in the dataset and the status message says so. ntfy and Pushover send at most 10 individual pushes, then one "...and N more" summary.
  • Cost: notifications are free. You pay only the usual price for delivered rows, exactly as without alerts. There is no extra charge for any channel.
  • Failures never fail the run. A deleted webhook or an unreachable service leaves the run successful and the dataset complete, and the status message says what happened, for example Notified: Discord 12, Slack 12, ntfy 10 (+summary). Teams failed (HTTP 404) - check the webhook URL. The logs show the channel name and HTTP status only, never a URL or token.
  • Your URLs, tokens and ntfy topic are stored as secret inputs. Anyone holding a webhook URL can post to that channel, so treat it like a password.
  • It is a monitor that notifies you. It never subscribes, comments, likes or logs in to YouTube. The messages go only to the destinations you set up.
  • Nothing changes by default. With no destination set, a run behaves exactly as before and the dataset rows are identical.

How it works

For each channel URL or @handle the Actor:

  1. Reduces it to the channel and requests one page per tab you selected: https://www.youtube.com/<channel>/videos, /shorts, /streams (the Live tab) and/or /channels (YouTube answers that address with the channel's Home page, where the channels it features sit). Each page carries the channel header and that tab's newest items, so the channel row is built once, from the first page read (tracking parameters and tabs in your link are dropped; the same channel listed twice is read once).
  2. Reads robots.txt once per run and checks every path - and every redirect - against it before requesting it.
  3. Reads the channel and the tab's items from the data embedded in the page's HTML, the same data YouTube's own page is drawn from.
  4. With discoverDepth: 1, adds the featured channels it found to the run (after the channels you listed, de-duplicated, capped) and reads each with the same tabs except channels.
  5. Delivers flat rows, charging only for rows it actually delivers.

It reads politely - about one request every 2 seconds, with an honest, self-identifying User-Agent - and it never signs in. If YouTube answers with a bot check or a consent page it reports that and stops, instead of working around it.

Input

FieldTypeDefaultDescription
channelUrlsstring[](required)YouTube channel URLs or @handles. Up to 5,000 per run. Video links are rejected.
tabsstring[]["videos"]Which tabs to read: any of videos, shorts, streams (the Live tab), channels (featured channels). Duplicates collapse; tabs are read in that fixed order.
discoverDepthinteger00: only the channels you list. 1: every featured channel found becomes an extra channel of the run (needs channels in tabs), read with the same tabs except channels - one hop, never deeper.
maxDiscoveredChannelsinteger201-200. Cap on the extra channels discovery adds. Ignored when discoverDepth is 0.
outputstringchannelsAndVideoschannelsAndVideos: a channel row plus a row per item on each selected tab. channelsOnly: just the channel row.
outputFormatstringfullfull: every field (default). compact: a smaller row for AI agents (see "Use with AI agents (MCP)"). Charging is identical.
maxItemsinteger1001-5,000. Hard cap on rows delivered in the run (channel, video, short, stream and featuredChannel rows together) - your cost cap.
onlyChangedSinceLastRunbooleanfalseChange mode: only new items per tab, plus a channel row when its displayed counts changed.
stateStoreNamestringyoutube-stats-tracker-stateName of the store that remembers what was seen in change mode. Letters, numbers and hyphens.
discordWebhookUrlstring (secret)(none)Discord webhook URL. Must look like https://discord.com/api/webhooks/<id>/<token>. Empty means no Discord messages. See the alerts section above.
telegramBotTokenstring (secret)(none)Telegram bot token from @BotFather. Needs telegramChatId.
telegramChatIdstring(none)Telegram chat to post to: a numeric chat id or @channelusername. The bot must be added to that chat or channel. Needs telegramBotToken.
slackWebhookUrlstring (secret)(none)Slack incoming webhook, https://hooks.slack.com/services/....
slackCompatibleWebhookUrlstring (secret)(none)Mattermost / Rocket.Chat incoming webhook (public https:// URL; Markdown list, no Block Kit).
teamsWebhookUrlstring (secret)(none)Microsoft Teams Workflows webhook (host ending .logic.azure.com or .powerplatform.com).
googleChatWebhookUrlstring (secret)(none)Google Chat space webhook, https://chat.googleapis.com/v1/spaces/....
ntfyTopicstring (secret)(none)ntfy topic name; setting it turns ntfy phone pushes on. Pick a long, unguessable name.
ntfyServerstringhttps://ntfy.shntfy server. Only change it for your own public https:// ntfy server.
ntfyTokenstring (secret)(none)Optional ntfy access token for a self-hosted server or protected topic. Needs ntfyTopic.
pushoverUserKeystring (secret)(none)Pushover user key. Needs pushoverAppToken.
pushoverAppTokenstring (secret)(none)Pushover application API token. Needs pushoverUserKey.
webhookUrlstring (secret)(none)Generic JSON webhook (Zapier, Make, n8n, Pipedream, IFTTT, Home Assistant): every delivered row, 100 per POST. Public https:// only.
maxNotificationsinteger251–100. Most updates sent to chat and phone channels per run (the webhook is not capped). Extra updates are still in the dataset. Only used when a destination is set.

Input details

The Store form and the MCP tool schema show a short description for each field (an AI agent sees only the first 500 characters of each); the longer rules are here.

  • channelUrls: accepted forms are https://www.youtube.com/@mkbhd, /channel/UC..., /c/<name>, /user/<name> or just the @handle. A tab on the end (/videos, /shorts, /featured) is ignored (choose the tabs in tabs), tracking parameters are cleaned up, and the same channel listed twice is read once. Up to 5,000 per run. Video links (watch, youtu.be, shorts) are NOT supported and stop the run with a clear message: YouTube asks automated clients to sign in on video pages and this Actor never bypasses that. Pass the channel URL instead.
  • tabs: videos is the Videos tab (about 30 latest), shorts the Shorts tab (about 48 latest), streams the Live tab (past streams) and channels the channels a creator features (usually 3 to 13, one featuredChannel row each, for influencer discovery). Each tab is one page request per channel and one billed row per item shown, plus a single channel row per channel however many tabs you pick. In change mode every tab is tracked on its own, so "new uploads only" works for Shorts and streams too, and a newly featured channel comes with isNew true; a tab you add later is delivered once as its first run and then only its new items.
  • discoverDepth: with 1, every featured channel found on the channels you list (needs channels in tabs) is also read as an extra channel of this run, with the same tabs except channels: one hop, never deeper. Discovered channels are de-duplicated by channel ID, capped by maxDiscoveredChannels, marked discoveredFrom (the channel that featured them) on their channel row, and count against maxItems like any other row. Same pacing and robots.txt rules.
  • maxDiscoveredChannels: each extra channel costs one request and at least one billed channel row, so together with maxItems this is your cost cap. Ignored when discoverDepth is 0.
  • output: channelsAndVideos returns one channel row plus one row per item on each selected tab (about 30 videos, 48 shorts or a channel's past streams, newest first). channelsOnly returns just the channel row. Every delivered row is one billed result, so a channel with 30 videos is up to 31 rows.
  • maxItems: you are charged $0.0013 per delivered row. One channel can be up to 31 rows with the Videos tab, 49 with Shorts, and about 84 with Videos, Shorts and Live; the channels tab adds one row per featured channel (usually 3 to 13), and each discovered channel at least one more.
  • onlyChangedSinceLastRun: delivers a row for each video, short or stream not seen before on that channel and tab (for the tabs you selected), plus a channel row when its displayed subscriber count or video count changed. Nothing unchanged is delivered or charged. The first run delivers everything and remembers it.
  • stateStoreName: only used in change mode; remembers which videos, shorts and streams and which subscriber and video counts were already seen for each channel, across scheduled runs. Leave the default unless several tasks must keep separate histories.

Output fields

Channel rows (type: "channel")

CategoryFields
IdentitychannelId, url, handle, name, avatarUrl, description (first 5,000 characters)
NumberssubscriberCountText + subscriberCount (approximate), videoCountText + videoCount (approximate)
Statusstatus (ok / unavailable), statusReason, changedFields (change mode), newVideoIds (change mode), scrapedAt
DiscoverydiscoveredFrom - only on channels found by discoverDepth: 1: the channelId of the channel that featured it

Video rows (type: "video", the Videos tab)

CategoryFields
IdentitychannelId, channelName, channelHandle, videoId, url, title
NumbersviewCountText + viewCount (approximate), publishedText, durationText + durationSeconds
Positionposition (1 = newest), isNew (change mode), scrapedAt

Short rows (type: "short", the Shorts tab): the same fields; url is https://www.youtube.com/shorts/<videoId>, and publishedText, durationText and durationSeconds are always null because a Shorts tile shows no date or length.

Stream rows (type: "stream", the Live tab): the video fields plus liveStatus ("past" for a finished stream, null when the page does not say). publishedText reads like "Streamed 8 years ago".

Featured channel rows (type: "featuredChannel", the Channels tab)

CategoryFields
SourcesourceChannelId, sourceHandle - the channel whose page features this one
IdentitychannelId, handle, name, url (https://www.youtube.com/channel/<channelId>)
NumberssubscriberCountText + subscriberCount (approximate), videoCountText (null when the page shows none, as for some small channels)
Positionposition (1 = first on the page), isNew (change mode), scrapedAt

In the Apify dataset view the four item types have their own tables: Videos, Shorts, Live streams and Featured channels. Missing values are null. Nothing is invented: subscriberCount is null when the channel hides its subscribers, a number that cannot be read from YouTube's text is null rather than a guess, durationSeconds is null for a live stream, and viewCount is null where a tab shows no views (a few old streams).

Use it as an API

from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("lukehunter/youtube-stats-tracker").call(
run_input={
"channelUrls": ["https://www.youtube.com/@mkbhd", "@veritasium"],
"tabs": ["videos", "shorts", "streams"],
"onlyChangedSinceLastRun": True,
}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
if item["type"] in ("video", "short", "stream") and item["isNew"]:
print("New upload:", item["channelName"], item["title"], item["url"])
elif item["type"] == "channel" and item["changedFields"] != ["firstSeen"]:
print("Channel changed:", item["name"], item["subscriberCountText"], item["changedFields"])

Use with AI agents (MCP)

This Actor works as a tool for Claude, ChatGPT, Cursor and other MCP clients through Apify's MCP server. The only input a call needs is channelUrls.

Claude Desktop (claude_desktop_config.json), with your own token from Apify Console > Settings > API & Integrations:

{
"mcpServers": {
"apify-youtube": {
"command": "npx",
"args": [
"-y", "mcp-remote",
"https://mcp.apify.com?tools=lukehunter/youtube-stats-tracker",
"--header", "Authorization: Bearer ${APIFY_TOKEN}"
],
"env": { "APIFY_TOKEN": "<YOUR_APIFY_TOKEN>" }
}
}
}

Clients that support OAuth (Claude.ai, VS Code) can connect with just the URL https://mcp.apify.com?tools=lukehunter/youtube-stats-tracker and sign in to Apify. Never paste a real token into a shared file or chat.

Example. You ask: "How many subscribers does @mkbhd have, and what are his latest videos and their views?" The agent calls the tool lukehunter--youtube-stats-tracker with:

{
"channelUrls": ["@mkbhd"],
"outputFormat": "compact",
"maxItems": 10
}

outputFormat: "compact" returns numeric counts (subscribers, views, duration in seconds), titles, URLs and ids without display strings, so the result fits an agent's context. It is charged exactly like the default "full" output: $0.0013 per row, capped by maxItems. For a stats check only, add "output": "channelsOnly" to get just the channel row.

Pricing and cost control

Pay per delivered row (a channel, a video, a Short, a stream or a featured channel).

$0.0013 per row - 1,000 rows = $1.30.

What you runRowsCost
1 channel, Videos tab (channel row + up to 30 videos)about 31about $0.0403
1 channel, Shorts tab (channel row + up to 48 Shorts)about 49about $0.0637
1 channel, Live tab (channel row + its past streams)about 6-12about $0.0078-$0.0156
1 channel, Videos + Shorts + Live (one channel row, @mkbhd: 30 + 48 + 5)about 84about $0.1092
1 channel, Featured channels tab (channel row + 3-13 featured channels)about 4-14about $0.0052-$0.0182
Creator list: 3 seed channels, tabs: ["channels"], discoverDepth: 1, default cap of 20 (3 + 30 featured + 20 discovered)53about $0.0689
100 channels, channels only100$0.13
100 channels, Videos tab (first run)about 3,100about $4.03
The same 100 channels on a schedule, change mode, per new video, Short or stream1$0.0013
Cap: 5,000 rows5,000$6.50

Discovery adds nothing to the price per row: every discovered channel is read and charged like a channel you listed yourself, and maxDiscoveredChannels and maxItems bound the cost. There is no charge for starting a run, and none for a URL that is blocked, skipped by robots.txt, invalid, or answered with a bot check or consent page. A channel YouTube answers 404 for is a real answer and is charged as one row. In change mode, unchanged channels and items are not charged. maxItems is a hard upper bound on cost, and the Actor never delivers a row your run's maximum-cost setting cannot pay for. If maxItems cuts a channel short, the channel row and the first tab's newest items are the ones kept.

Notifications to Discord, Telegram, Slack, Teams, Google Chat, ntfy, Pushover and webhooks are free: you pay only for delivered rows, never for messages sent.

Limitations

  • Channel tab pages only, and what each tab shows first. The Actor reads one page per selected tab, <channel>/videos, /shorts, /streams and/or /channels, and returns the items YouTube lists on it (about 30 videos, 48 Shorts, a channel's past streams - newest first). It does not page back through a channel's whole catalogue, because that needs YouTube's "load more" API (/youtubei/), which robots.txt disallows. To follow a channel's output over time, run it on a schedule in change mode: each new upload, Short or stream is delivered as it appears.
  • No video pages, search, comments or transcripts. Video pages are not read because YouTube asks automated clients to sign in there ("Sign in to confirm you're not a bot"), and this Actor never bypasses that. Search (/results), comments (/comment) and the paging API are disallowed by YouTube's robots.txt. So there are no exact view counts, like counts, descriptions, tags or publish dates for individual videos: you get what the channel's tab pages show.
  • Discovery follows what creators feature, not YouTube's recommendations. The Featured channels tab lists the channels a creator chooses to show on their own page (their other channels, collaborators, friends - 3 to 13 on the pages we have read). Many channels feature none, so they add no rows; there is no "similar to" ranking, no search for channels by topic (/results is disallowed by robots.txt) and no paging for more. YouTube answers the /channels address with the channel's Home page, and that page's featured-channel shelf is what is read. Discovery goes one hop (discoverDepth 1): discovered channels are read, never expanded further. Discovered channels are added in the order found (your channels first, in input order, then each page's order) up to maxDiscoveredChannels.
  • Numbers are as YouTube displays them, rounded. Subscribers ("21.3M subscribers"), the channel's video count and each video's views ("6.1 million views") are rounded by YouTube, so the integer fields are approximate, and a subscriber change is only seen when the displayed figure moves. A video's age is relative ("3 days ago"), not a timestamp.
  • robots.txt is respected, for every path and every redirect. A disallowed path is skipped and reported, and a robots.txt that cannot be read means nothing is read without confirmed permission.
  • YouTube can answer with a bot check or a consent page. From some networks YouTube sends a bot check or a consent page instead of the channel. The Actor reports it (blocked or consent-required), delivers no row for it, charges nothing, and stops sending requests for the rest of the run. It does not sign in, use cookies, proxies or rotating identities, click through consent, or solve anything. Rows already delivered earlier in the same run are kept and charged normally.
  • Shorts and live streams. A Shorts tile shows only a title and a view count, so Short rows have no date or length (null). The Live tab lists streams with "Streamed ... ago" text; only finished streams have been seen on a channel page, so liveStatus is "past" for those and null otherwise - a stream that is live now or scheduled is never guessed. A channel with no Shorts or Live tab simply adds no rows for that tab. When a video's length text is not a length (for example a live stream), durationSeconds is null.
  • Language and region follow the page YouTube serves. The Actor asks for English; if YouTube answers in another language the text fields keep that text, and a count that cannot be read from it is null.
  • Change mode compares against the last successful run. The first run delivers everything. A video, Short or stream that merely leaves a tab's first page, or one that is deleted, is not a change. A change that happens and reverts between two runs is not seen.
  • Reads about 1 page per 2 seconds, so 1,000 channels take roughly 35 minutes on one tab - and each extra tab adds a page per channel, and each discovered channel a page per tab.
  • This is an independent tool, not affiliated with, endorsed by, or connected to YouTube or Google. You are responsible for using the collected data in line with YouTube's terms and applicable law.

Use it from your code, Make, Zapier or n8n

Built for automated pipelines: small runs, stable field names, one row per item, and you pay only for rows actually delivered.

One API call that runs the Actor and returns the data (replace $APIFY_TOKEN with your token from Apify Console → Settings → Integrations):

curl -s -X POST "https://api.apify.com/v2/acts/lukehunter~youtube-stats-tracker/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"channelUrls": ["https://www.youtube.com/@mkbhd", "@veritasium"], "tabs": ["videos"], "maxItems": 20}'

JavaScript (npm i apify-client):

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('lukehunter~youtube-stats-tracker').call({
"channelUrls": [
"https://www.youtube.com/@mkbhd",
"@veritasium"
],
"tabs": [
"videos"
],
"maxItems": 20
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Python (pip install apify-client):

import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('lukehunter~youtube-stats-tracker').call(run_input={"channelUrls": ["https://www.youtube.com/@mkbhd", "@veritasium"], "tabs": ["videos"], "maxItems": 20})
items = client.dataset(run['defaultDatasetId']).list_items().items

Schedules, webhooks and no-code tools: save your input as a Task, add a Schedule (e.g. hourly or daily), and attach a webhook or one of the Integrations tab's connectors (Make, Zapier, n8n, Google Sheets, Slack) to push each run's results into your own system.

Only new items each run: set "onlyChangedSinceLastRun": true on a scheduled Task and every run after the first delivers, and charges for, only items it hasn't delivered before for that same input. That makes it a ready-made change feed.

Reliability: an automated test runs this Actor's parser against the live site every day, and failures are fixed fast.

Feedback: an honest rating on the Store page, good or bad, helps others decide if this Actor fits their needs. Missing a field or found a problem? Open an issue on the Actor page.

FAQ

YouTube asks automated clients to sign in to confirm they are not a bot on video pages, and this Actor never bypasses that. Paste the channel's URL or @handle instead: the Videos page lists its latest uploads, and change mode alerts you to new ones.

How do I get alerted when a channel posts a new video?

Set onlyChangedSinceLastRun to true (add shorts and streams to tabs to cover them), save the input as a Task, add a Schedule and connect Slack, Google Sheets or a webhook - see "Alert on new uploads" above. Each run delivers a row with isNew: true for every upload it had not seen, and nothing otherwise.

Can I get every video on a channel?

No. The Videos tab lists the ~30 newest (the Shorts tab ~48), and going further needs YouTube's paging API, which robots.txt disallows. Run on a schedule in change mode and you collect each new upload as it appears.

Can I monitor a channel's Shorts and live streams too?

Yes: set tabs to ["videos", "shorts", "streams"] (any combination). Short rows (type: "short") carry the title, link and views; stream rows (type: "stream") the video fields plus liveStatus. A Short has no date or length on the channel page, so those are null. In change mode each tab is tracked separately, so you are alerted to new Shorts and new streams as well as new videos.

How do I find similar channels or build a list of influencers?

Add "channels" to tabs to get, for each channel you give, the channels it features (one featuredChannel row each, with name, @handle, subscribers and video count). Set discoverDepth to 1 and each of those channels is also read as a channel row (discoveredFrom tells you which seed featured it), so three seeds in a niche become a creator list in one run - see the recipe above. It is the channels that creators themselves feature, not an algorithmic "similar channels" list, and it goes one hop deep.

Will discovery keep crawling from channel to channel?

No. discoverDepth: 1 reads the channels featured by the channels you listed, and stops: the channels those discovered channels feature are never fetched. maxDiscoveredChannels (default 20, up to 200) and maxItems bound a run further.

I switched on a tab for a channel I already watch - will I get 48 "new" alerts?

No. A tab the watch has never seen for that channel is delivered once as its first run (isNew: null) and remembered; only items that appear after that are isNew: true. Watches created before the tabs option keep working unchanged.

Are the view and subscriber counts exact?

They are what YouTube shows on the channel page, which rounds them ("21.3M subscribers", "6.1 million views"). The text is kept as shown and the number parsed from it is approximate.

Is the first run charged?

Yes, normally: it delivers everything (the channel and the items of the tabs you selected) and remembers it. After that you pay only for new videos, Shorts and streams and for channel rows whose displayed counts changed.

What counts as a change?

For videos, Shorts and streams: an ID not seen before for that channel and tab. For channels: the displayed subscriber count, the displayed video count or the status. Views, titles and thumbnails of videos already seen are ignored.

YouTube answered with a bot check or a consent page instead of the channel. Nothing was read, nothing was charged, and the run stopped sending requests. See the Limits section.

/@handle, /channel/UC..., /c/<name>, /user/<name> or a bare @handle, with or without a tab. Query strings and tracking parameters are removed, and the same channel listed twice - even as a handle and as a channel ID - is read, and charged, once. Video, search, playlist, feed and YouTube Music links are reported as unsupported.

Do I need a YouTube API key?

No. It reads the public channel page, so there is no key and no API quota to manage - and no access to anything that is not on the public page.

Can I get YouTube new video alerts in Discord, Telegram, Slack or on my phone?

Yes. Set discordWebhookUrl, telegramBotToken + telegramChatId, slackWebhookUrl, teamsWebhookUrl or googleChatWebhookUrl, or use ntfyTopic (free, no account) or pushoverUserKey + pushoverAppToken for phone pushes. Mattermost and Rocket.Chat work through slackCompatibleWebhookUrl. Alerts need change mode (onlyChangedSinceLastRun) and a schedule: each run then sends only the new videos, Shorts and streams it delivered (plus channel changes), never the ones you have already seen. Setup steps for each are in the alerts section above. Notifications cost nothing extra.

Can I get YouTube new video alerts by email?

Not natively. There are two routes. (1) Set webhookUrl to a Zapier, Make or n8n catch hook and add an email or Gmail step: the webhook delivers every row as JSON, so the email can list the updates. (2) Apify's own run integrations and run notifications can email you when a run finishes, but they only send a link to the run or dataset, not the updates themselves. For instant alerts without building anything, Telegram, ntfy or Pushover reach your phone directly.

Can I get YouTube new video alerts in WhatsApp?

Not natively, and we do not use unofficial WhatsApp APIs, which risk getting your number banned. The official route is the WhatsApp Business Platform (Cloud API) with its own business account, message templates and pricing: point webhookUrl at a Zapier, Make or n8n flow that calls it. For free phone alerts use Telegram or ntfy instead.

Other data tools from the same developer, built to the same standard: official or public sources, hard cost caps, and honest documentation of limits.