# YouTube Channel Videos Scraper — Exact Stats & Outliers (`foxlabs/youtube-channel-videos-scraper`) Actor

The videos of any YouTube channel — Videos, Shorts, Live — with unrounded views, likes, publish date and duration, and an outlier score that shows which videos beat the channel's usual. Comment count, description and chapters on request. Only-new mode for monitoring. No login, no API key.

- **URL**: https://apify.com/foxlabs/youtube-channel-videos-scraper.md
- **Developed by:** [Berkan Kaplan](https://apify.com/foxlabs) (community)
- **Categories:** Social media, Videos
- **Stats:** 4 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 1,000 video with exact figures

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## YouTube Channel Videos Scraper — Exact Stats & Outliers

**foXLabs YouTube series:** [Channel Email Scraper](https://apify.com/foxlabs/youtube-channel-email-scraper) · [Channel Finder](https://apify.com/foxlabs/youtube-channel-finder) · [Search Scraper](https://apify.com/foxlabs/youtube-search-scraper) · [Comments Scraper](https://apify.com/foxlabs/youtube-comments-scraper)

📼 Get every video on a YouTube channel's Videos, Shorts and Live lists — as far back as the lists go — with the figures YouTube rounds on the channel page: the unrounded view count, the like count and the publish date, plus an **outlier score** that shows how each video did against the channel's usual.
Built for content strategy, competitor tracking, upload monitoring and sponsorship checks. No YouTube API key, no login, no cookies.

### 🔍 What is YouTube Channel Videos Scraper — and when should you use it?

Give this Actor **channels** — URLs, @handles or channel IDs — and it returns one row per video from the channel's own Videos, Shorts and Live tabs, in the channel page's own orders (newest, most popular, oldest), read live. You choose how much is read for each video: its exact figures (the default), its whole page, or nothing beyond the channel's list and feed.

**Use it when you need:** a channel's whole archive with exact views, likes and dates; the videos that beat the channel's usual; a daily feed of new uploads from many channels; what a creator's recent videos really get before you sponsor them.

**Use something else when:** you need videos by keyword rather than by channel ([YouTube Search Scraper](https://apify.com/foxlabs/youtube-search-scraper)), the text of the comments ([YouTube Comments Scraper](https://apify.com/foxlabs/youtube-comments-scraper)), channels you do not know yet ([YouTube Channel Finder](https://apify.com/foxlabs/youtube-channel-finder)) or a creator's e-mail ([YouTube Channel Email Scraper](https://apify.com/foxlabs/youtube-channel-email-scraper)); or transcripts, tags and categories — this Actor does not read them.

### 🤖 Use with AI agents

**Already on the Apify MCP server?** Ask for this Actor by name: `foxlabs/youtube-channel-videos-scraper`. Checked on 2026-10-08: the server lists it as the tool `foxlabs--youtube-channel-videos-scraper`.

**Your agent can pay for its own runs.** The Actor is pay-per-event only, runs with limited permissions and does not use Standby — Apify's conditions for agentic payments — and Apify Store lists it as eligible for them (read from the Store on 2026-10-08). For an agent with a wallet and no human to sign in, Apify sells a prepaid, spend-capped API token over **x402** at `agi.apify.com`; the terms of that path are Apify's own (see `apify.com/agents.md`). Billing is the same either way: per delivered video row; status rows are free.

Otherwise paste this into Claude, ChatGPT, Cursor or any MCP-enabled assistant:

```
I want the videos of YouTube channels from the Apify Actor `foxlabs/youtube-channel-videos-scraper`.
Input: `channels` is a list of channel URLs, @handles or channel IDs. `videoTypes` picks the lists to read ("videos", "shorts", "live"). `maxVideosPerChannel` caps the videos per channel and kind, `maxVideos` the rows in total. `sort` is "latest", "popular" or "oldest". `details` is "figures" (exact views, likes, date — the default), "full" (plus comment count, description, chapters) or "list" (the channel's list and feed only). `minOutlierScore` keeps the videos that beat the channel's usual.
Start with: {"channels":["@mkbhd"],"maxVideosPerChannel":30}
Ask me which channels, run the Actor, then show the videos as a table sorted by outlier score.
```

The machine-readable API, MCP config and OpenAPI definition live at `apify.com/foxlabs/youtube-channel-videos-scraper.md`.

### 📋 Overview

Everything you need to turn a YouTube channel into a table of its videos — in one Actor, with no login, cookies or API key.

**Why pick this Actor:**

- 📼 **The whole archive** — the Videos, Shorts and Live tabs are followed to their end; the longest list we followed had 4,641 videos.
- 🔢 **Unrounded figures** — where the channel page said "5.5M views · 7 days ago", a row read on 2026-10-07 said 5,546,510 views, 124,423 likes, published 2026-09-30 14:42:19 UTC.
- ⚡ **Fast** — 1,852 videos with exact figures in 35 to 44 seconds; the latest uploads of 50 channels in 10.
- 📈 **Outlier score** — each video's views divided by the median of the channel's latest videos of the same kind: 1 is the usual, 5 is a hit. Filter on it to get only the videos that broke out.
- 🔁 **Only what is new** — a named list remembers what was delivered; the next run brings only the videos it has not delivered before: a monitoring feed for many channels. (A run takes up to 10,000 channel lines and a list remembers its newest 500,000 videos; the largest run we have measured had 50 channels.)
- 💰 **Pay per row, by depth** — $0.40 per 1,000 videos from lists and feeds, $0.60 with exact figures, $1.00 with the whole page. Status rows and filtered-out videos are free.
- 🤖 **Agent-ready** — on the Apify MCP server and open to agentic payments (x402).

### ✨ Features

- 📋 **Three lists** — `videoTypes`: `videos`, `shorts`, `live`; each followed until YouTube offers no further page.
- 🔀 **The channel page's own orders** — `sort`: `latest`, `popular`, `oldest`.
- 🔬 **Three depths** — `details`: `figures` (exact views, likes, date), `full` (plus comment count, description, hashtags, chapters), `list` (lists and feed only — about four requests a channel).
- 🧹 **Filters** — `publishedWithinDays`, `minViews`, `minOutlierScore`. A video the list already rules out is dropped without a request.
- 🔁 **Only new** — `onlyNewVideos` with a `listName`: scheduled runs deliver what the list has not delivered before.
- 🔴 **Live, upcoming, members-only told apart** — `isLive` with the audience in `liveViewers`, `isUpcoming`, `wasLive`, `isMembersOnly`.
- 🧾 **A last word in every run** — the status message says what was delivered and what the run knows was not: filtered out, cut by a limit, refused by YouTube.

### 🎬 Quick Start

```bash
curl -X POST "https://api.apify.com/v2/acts/foxlabs~youtube-channel-videos-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"channels":["@mkbhd"],"maxVideosPerChannel":30}'
```

The answer is the dataset itself: the channel's 30 latest videos with exact figures, usually after four to six seconds of run time.

### 🚀 Getting Started (3 steps)

1. **Choose your channels** — paste channel URLs, @handles or IDs into **Channels**.
2. **Set the depth and the limits** — `details` decides what is read for each video; `maxVideosPerChannel` (default 30) and `maxVideos` (default 1,000) cap the rows.
3. **Run and export** — download the dataset as JSON, CSV, Excel, HTML or XML. To keep watching, tick **Only videos not delivered before**, give the list a name and put the run on a schedule.

### 📥 Input

```json
{"channels":["@mkbhd"],"maxVideosPerChannel":30}
```

| Field | Type | Description |
|---|---|---|
| `channels` | array | Channel URLs, @handles or channel IDs, one per line (up to 10,000). |
| `videoTypes` | array | Which lists to read: `videos`, `shorts`, `live` (default `["videos"]`). |
| `maxVideosPerChannel` | integer | How many videos per channel **and kind** (default 30). |
| `maxVideos` | integer | How many rows in total (default 1,000). |
| `sort` | string | `latest` (default), `popular` or `oldest` — the channel page's own orders. |
| `details` | string | What to read for each video. `figures` (default): its unrounded views, likes and publish date — one small request. `full`: its own page — the same, plus the number of comments, the description, hashtags and chapters; slower, and limited by YouTube (see *How it works*). `list`: nothing beyond the channel's list and feed. |
| `publishedWithinDays` | integer | Only videos published in the last N days. Exact with `figures` and `full`. With `list` a video is judged by what the list shows: "1 month ago" passes 30 days, and a video the list shows no date for is left out, with the count in the status message. Streams and premieres that have not started are left out at every level. |
| `minViews` | integer | Only videos with at least this many views. With `details: "list"` an older video is judged by the list's figure, which is rounded down: one just above the minimum can be left out. |
| `minOutlierScore` | string | Only videos with at least this outlier score (decimals allowed: `"1.5"`). |
| `onlyNewVideos` | boolean | Remember delivered videos under `listName` and skip them in later runs. With this on, `maxVideosPerChannel` is how far down each list the Actor looks. Default off. |
| `listName` | string | Name of that memory: letters, digits and hyphens, up to 40 characters (default `default`). |
| `proxyConfiguration` | object | Nothing to change for normal use: requests go out directly, and a kind of request YouTube refuses is moved to Apify Proxy by the Actor itself. |

**More recipes**

- A channel's full archive, with exact figures: `{"channels":["@jameshoffmann"],"videoTypes":["videos","shorts","live"],"maxVideosPerChannel":5000,"maxVideos":5000}`
- Everything about a channel's latest videos — comment counts, descriptions, chapters: `{"channels":["@jameshoffmann"],"maxVideosPerChannel":50,"details":"full"}`
- The videos that broke out — most popular first, at least three times the channel's usual: `{"channels":["@51Drones","@LabMuffinBeautyScience"],"sort":"popular","maxVideosPerChannel":200,"minOutlierScore":"3"}`
- New uploads of many channels, every day — lists and feeds only, nothing delivered twice: `{"channels":["@mkbhd","@jameshoffmann","@51Drones"],"videoTypes":["videos","shorts"],"maxVideosPerChannel":15,"details":"list","onlyNewVideos":true,"listName":"watchlist"}`

### 📤 Output

One row per video (`type: "video"`). A line that brought nothing — not a channel, a channel without videos — comes back as a free row with `type: "status"` and the reason in `error`. (A channel line whose videos were all filtered out, or that the run did not reach, gets no row of its own: the status message counts those.) A run whose filters removed every video writes one such row with the run's last word, so a dataset never comes back empty without a reason (an "only new" run that finds nothing new is the exception). Every row has the same 44 columns.

| Field | Description |
|---|---|
| `type` | `video`, or `status` |
| `videoId` / `videoUrl` / `title` | The video. `videoId` is the stable key; a video appears once per run |
| `videoType` | `video`, `short` or `live` — the list it came from |
| `channelId` / `channelUrl` / `channelHandle` / `channelTitle` | The channel |
| `channelSubscribers` / `channelCountryCode` | Subscriber count as YouTube publishes it (rounded), and the country the channel states |
| `publishedAt` | Publish time to the second (UTC). Filled for the channel's latest 15 uploads, which its public feed lists; `null` for older videos |
| `publishedDate` | Publish date: the UTC date when `publishedAt` is known; otherwise the date YouTube shows on the video's page, which is in US Pacific time and can be one day earlier than the UTC date. A video less than a day old gets the UTC day its hour falls on, and `null` when that hour holds midnight. With `details: "list"` it is `null` for videos that are not in the feed, except uploads of the last day, which the list dates from "7 hours ago" |
| `publishedText` | What YouTube shows: "Oct 2, 2026" (the video itself) or "4 days ago" (the channel's list). Shorts carry no date in their list |
| `daysSincePublished` | Whole days since `publishedAt`; where only `publishedDate` is known, since noon of that day — so an upload of the last day can read 0 or 1 |
| `durationSeconds` | Length of the video, from the channel's list. `null` for Shorts (their list shows none) and for streams that are live or have not started |
| `views` / `viewsAreExact` / `viewsText` | The view count. `viewsAreExact: true` when it is unrounded; `false` when only the list's rounded figure is known ("9.4M views" → 9400000). `views` is `null` where YouTube shows no count: members-only videos, streams that have not started, most streams that are on air (one that the channel's feed lists carries the feed's count so far; its audience is in `liveViewers`), a few old streams and YouTube Originals episodes |
| `liveViewers` | For a stream that is live right now: how many are watching |
| `likes` | Unrounded like count; `0` when nobody has liked the video yet; `null` when it was not read or the channel hides its like counts |
| `commentsCount` / `commentsCountIsRounded` / `commentsOff` | `details: "full"` only. Number of comments (`0` when nobody has commented yet); YouTube shortens counts of 1,000 and more ("6.1K" → 6100, `commentsCountIsRounded: true`). `commentsOff: true` when the video's page shows no comment section: comments are turned off, or the video is for members only |
| `likeRate` / `viewsPerDay` / `viewsPerSubscriber` | Likes ÷ views (0.02 = 2%) · views ÷ days since publication · views ÷ the channel's subscribers |
| `channelMedianViews` | The usual for this channel and kind of video: the median view count of the first page of that list in newest-first order (about 30 videos; 48 for Shorts). `null` when fewer than three videos on that page are over and show a view count |
| `outlierScore` | `views` ÷ `channelMedianViews`. 1 = the channel's usual; 3 = three times that |
| `description` | The video's description, with every link as a full address. `details: "full"`; without it, filled for the latest 15 uploads |
| `hashtags` | `details: "full"` only. The hashtags YouTube shows above the title (up to three) |
| `chapters` / `chaptersAutoGenerated` | `details: "full"` only. `title` and `startSeconds` of each chapter: the creator's own (`false`) or the ones YouTube generates itself (`true`) |
| `isLive` / `wasLive` / `isUpcoming` | Live right now · a finished live stream · a stream or premiere that has not started |
| `isMembersOnly` | A video for the channel's paying members |
| `thumbnailUrl` | YouTube's standard thumbnail address for the video |
| `position` / `sort` | The video's place in the channel's list (1 = first) in the order you asked for |
| `detailLevel` | What was read for the row: `figures`, `full` or `list` |
| `input` / `scrapedAt` / `error` | The channel line the row belongs to · when the row was made · `null` on video rows, the reason on status rows |

Sample — a row as the Actor returned it on 2026-10-07 with `details: "full"`, every column, nothing shortened:

```json
{
  "type": "video",
  "videoId": "e1q-TuHdc4Y",
  "videoUrl": "https://www.youtube.com/watch?v=e1q-TuHdc4Y",
  "title": "Dear YouTube!",
  "videoType": "video",
  "channelId": "UCBJycsmduvYEL83R_U4JriQ",
  "channelUrl": "https://www.youtube.com/channel/UCBJycsmduvYEL83R_U4JriQ",
  "channelHandle": "@mkbhd",
  "channelTitle": "Marques Brownlee",
  "channelSubscribers": 21400000,
  "channelCountryCode": "US",
  "publishedAt": "2026-09-30T14:42:19.000Z",
  "publishedDate": "2026-09-30",
  "publishedText": "Sep 30, 2026",
  "daysSincePublished": 7,
  "durationSeconds": 743,
  "views": 5557856,
  "viewsAreExact": true,
  "viewsText": "5,557,856 views",
  "liveViewers": null,
  "likes": 124581,
  "commentsCount": 9500,
  "commentsCountIsRounded": true,
  "commentsOff": false,
  "likeRate": 0.0224,
  "viewsPerDay": 793979,
  "viewsPerSubscriber": 0.26,
  "channelMedianViews": 5104379,
  "outlierScore": 1.09,
  "description": "Let's talk about this new video A/B testing feature\n\nThe Waveform podcast: https://www.youtube.com/watch?v=UD9X5WxZ5nE&t=3870s\n\nMKBHD Merch: http://shop.MKBHD.com/\n\nPlaylist of MKBHD Intro music: https://goo.gl/B3AWV5\n\n~\nhttp://twitter.com/MKBHD\nhttp://instagram.com/MKBHD\nhttp://facebook.com/MKBHD",
  "hashtags": [],
  "chapters": [{ "title": "The new YouTube AB testing", "startSeconds": 0 }, { "title": "How video testing works", "startSeconds": 69.84 }, { "title": "Concerns and questions", "startSeconds": 147.04 }, { "title": "Insights from YouTube engineers", "startSeconds": 253.04 }, { "title": "Community impact and trends", "startSeconds": 378.16 }, { "title": "YouTube's competitive strategy", "startSeconds": 460.319 }, { "title": "Advice for fellow creators", "startSeconds": 538.8 }],
  "chaptersAutoGenerated": true,
  "isLive": false,
  "wasLive": false,
  "isUpcoming": false,
  "isMembersOnly": false,
  "thumbnailUrl": "https://i.ytimg.com/vi/e1q-TuHdc4Y/hqdefault.jpg",
  "position": 2,
  "sort": "latest",
  "detailLevel": "full",
  "input": "@mkbhd",
  "scrapedAt": "2026-10-07T22:22:47.851Z",
  "error": null
}
```

With the default `details: "figures"` the same video comes with the same views, likes and dates; `commentsCount`, `commentsCountIsRounded` and `commentsOff` are `null`, `hashtags` and `chapters` are empty, and `description` is filled only because the video is among the channel's latest 15 uploads.

### 🧠 How it works

For each channel the Actor reads its About panel (subscribers, country, number of videos) and then up to four things, cheapest first; a better source replaces what a cheaper one said:

1. **The channel's lists** — its Videos, Shorts and Live tabs, about 30 videos a request (48 for Shorts), in the order you asked for. A list gives the title, the duration and YouTube's rounded figures: "9.4M views", "4 days ago".
2. **The channel's public feed** — one request (asked again, up to five times, when it does not come). It covers the latest 15 uploads with unrounded views and likes, the description and the publish time to the second.
3. **Each video's figures** (`details: "figures"`, the default; one small request per video) — what a video's page keeps refreshing while it is open: the unrounded view count, the like count and the publish date.
4. **Each video's own page** (`details: "full"`, one large request per video) — the same figures, and the number of comments, the description, hashtags and chapters.

With `details: "list"` a channel costs about four requests however many of its latest videos you take from the first page: its latest 15 uploads still come with the feed's unrounded counts — unless the feed lags behind the list, as it can for an upload of the last hours: then the list's larger, rounded figure is kept — and older ones with the list's rounded ones (`viewsAreExact` says which). That is the mode for watching many channels.

**YouTube limits video pages; in our measurements it did not limit figures.** One address may read roughly a thousand video pages in a row; then YouTube refuses it for a while (in our measurement at the 1,170th request of a run; some runs were refused much sooner — one at its 308th request, a few at their first pages — which fits an address that earlier runs had used). The Actor then sends page requests through Apify Proxy, where they are slower, and keeps everything else direct. If no address is accepted any more, the remaining videos are not left out: they are delivered with their exact figures (`detailLevel: "figures"`, the cheaper event), and the status message says how many. Figures showed no such limit: 6,104 in a row in one run, none refused. For an archive of thousands of videos use `figures`, or expect `full` to slow down after the first thousand.

**The outlier score** compares a video with what is usual for the channel: the median view count of the first page of the same list in newest-first order — whatever order you asked the videos in. Long videos are compared with long videos, Shorts with Shorts. Recent uploads are still collecting views, so a video from last week scores lower than it eventually will.

**Sorting.** `popular` and `oldest` are the channel page's own sort options. A channel with only a few videos shows none; its short list is then put in order by the Actor. A longer list that offers no such option comes newest first, and the status message says so. Rows of one channel come in the order of its lists: videos, then Shorts, then live streams.

**Filters before requests.** A video the list already rules out — "2 years ago" against `publishedWithinDays: 30`, "9.4K views" against `minViews: 100000` — is dropped without a request. With the newest-first order the Actor stops reading a list once five finished videos in a row are past the date limit (with `details: "list"`, a video without a date counts as past it); streams that are live or have not started are not counted, because a Live list keeps them on top however long they have been running.

**Only new videos.** A list name remembers what was delivered. On the next run the Actor looks at the top of each list — the first `maxVideosPerChannel` — and delivers the videos there that it has not delivered before. It does not walk further back to fill the number with older videos, so set `maxVideosPerChannel` at least as high as the number of uploads a channel can make between two runs. Two things follow from "not delivered before": a video that did not pass your filters last time and passes now is delivered now (a video that has just crossed your `minOutlierScore`, for example), and when a video at the top of a list disappears — deleted or made private — an older one moves into view and is delivered once. Add `publishedWithinDays` if you want fresh uploads only.

**Lists are followed until they end** — until YouTube offers no further page. A list that stops answering part-way is not passed off as complete: what it brought is delivered, and the status message says the list was cut.

### 🧪 Data quality — measured

Measured on 2026-10-07: the table, the archive figures and the Live-list bullet in runs on the Apify platform; the comparison of figures with pages, the description links and the hashtags with probes through Apify Proxy and on recorded answers.

| | `figures`: one channel's whole archive (1,852 rows: 1,724 videos, 123 Shorts, 5 streams) | `full`: one channel's latest 300 videos | `list`: latest 15 videos and 15 Shorts of 50 channels (1,414 rows) |
|---|---|---|---|
| Unrounded view count | 1,845 of 1,852 | 300 of 300 | 797 of 1,414 — rows that are in their channel's feed, and rows whose list figure is below 1,000 and so not rounded; the list's rounded figure for 616 |
| Like count | 1,838 of 1,852 | 300 of 300 | 741 of 1,414 |
| Publish date | 1,852 of 1,852 | 300 of 300 | 741 of 1,414 |
| Duration | 1,729 of 1,729 videos and streams | 300 of 300 | 713 of 713 videos |
| Outlier score | 1,845 of 1,852 | 300 of 300 | 1,412 of 1,414 |
| Number of comments | — | 300 of 300 | — |
| Description | latest 15 uploads | 300 of 300 | 553 of 1,414 |
| Chapters | — | 279 of 300 | — |
| Unique videos | 1,852 of 1,852 | 300 of 300 | 1,414 of 1,414 |

- **Where a count is missing, YouTube shows none.** The seven rows without a view count in the first column are six episodes of a YouTube Originals series and a live stream from 2013: their pages show a date and no view count. The one row without views in the third column is a members-only Short. Of the 14 rows without a like count in the first column, six are those Originals episodes and seven are videos from 2009 to 2011; the fourteenth, a Short of 2024 with 3.9 million views, we did not check on its page.
- **Figures and pages say the same.** Read at the same moment, five videos of five kinds (a video, a Short, a members-only Short, a live stream, a stream that had not started) gave the same views, likes and date both ways. Thirty more, read both ways an hour apart, had identical publish dates; their view counts stood between 9 lower and 495 higher, their like counts between 1 lower and 8 higher.
- **A larger archive.** 6,104 videos of one channel (4,641 videos, 882 Shorts, 581 streams) with `figures`: 6,098 with a view count, 6,103 with a date, no request refused. The one video without a date had been uploaded eight minutes before the run: its page said "8 minutes ago" and the feed did not list it yet. (That was build 0.1.6; since 0.1.11 such a video gets the UTC day its minute falls on.)
- **Live lists.** 55 streams of two channels: 25 were live (each with its audience in `liveViewers`), 2 had not started (no date, no views, `isUpcoming`), 28 were over (`wasLive`).
- **Description links.** On a video's page YouTube shows some links in another form: a social profile as an icon and "/ name", a long address cut short, a link to another video as "• Its Title…". In 93 descriptions we recorded, 264 of 817 links to addresses were shown as "/ name" (194) or cut short (70), and 88 links to other videos as "• Its Title…". The Actor puts the full addresses back; a link to a moment in another video keeps its start time. The text can still differ from what the creator typed in small ways (YouTube writes `youtu.be` links out in full and drops `*bold*` marks).
- **Hashtags** are the ones YouTube shows above a video's title, which it does for about one video in twenty in our records (123 of 2,616 read at page level) and for up to half on some channels (60 of 122 on one): `hashtags` came back empty for all 300 videos of the second column. On seven videos that show them, all were read.

### ⏱️ Performance — measured

On the platform with the default 1,024 MB, 2026-10-07.

- 30 videos with exact figures (the prefilled input): **4 to 6 seconds** in five runs.
- 300 videos with exact figures: **6 to 8 seconds**. A whole channel of 410 videos, Shorts and streams: **6 to 7 seconds**. 1,852: **35 to 44 seconds** — 1,919 requests, none refused. (Three runs each.) 6,104, in one run: **110 seconds**.
- 300 videos with `details: "full"`: **32 to 50 seconds** in seven runs from an address YouTube accepted. Where YouTube refused the address for video pages from the start, the pages went through Apify Proxy at about a page a second or slower — 50 pages in 50 seconds in one run, 39 in a minute in another — while lists and feeds stayed direct.
- The latest 15 videos and 15 Shorts of 50 channels with `details: "list"`: 1,414 rows in **10 seconds** — 207 requests, about four a channel.
- The videos that broke out across five channels (3,038 videos on the lists, 433 read, 395 delivered): **16 seconds**.

YouTube's own answer times move through the day, which is why the same run can take 35 seconds once and 44 the next time. A run the platform restarts continues from the rows it already delivered: a test run of 300 videos with `details: "full"` was restarted on purpose after 92 rows and ended with 300 different videos.

### 💼 Use cases

**1. Content strategy** — find what works on a channel.
*Input:* a channel with `sort: "popular"` and `minOutlierScore: "3"`. *Output:* the videos that got at least three times the channel's usual, with views, likes and dates. *Use:* see what the hits have in common — titles, length, chapters.

**2. Competitor tracking** — the same, for the channels you compete with.
*Input:* a list of channels on a schedule. *Output:* each channel's latest videos with exact figures and outlier scores. *Use:* a weekly report of who published what and how it did.

**3. Upload monitoring** — a feed of new videos from many channels.
*Input:* channels with `details: "list"`, `onlyNewVideos` and a `listName`, scheduled daily. *Output:* only the videos the list has not delivered before. *Use:* alerts, newsletters, a "new this week" table.

**4. Sponsorship checks** — what a creator's recent videos really get.
*Input:* the creator's channel with `maxVideosPerChannel: 30`. *Output:* exact views per video, `channelMedianViews`, `viewsPerSubscriber`. *Use:* price a sponsorship on the median of recent videos, not on the subscriber count.

**5. Research datasets** — a channel's archive.
*Input:* a channel with all three `videoTypes` and high limits. *Output:* every video with exact figures; with `details: "full"`, descriptions with every link as a full address. *Use:* longitudinal studies, link analysis, training data.

### 🔗 Integration

**JavaScript / Node.js**

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('foxlabs/youtube-channel-videos-scraper').call({"channels":["@mkbhd"],"maxVideosPerChannel":30});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const row of items.filter((r) => r.type === 'video')) console.log(row.publishedDate, row.views, row.outlierScore, row.title);
```

**Python**

```python
from apify_client import ApifyClient
client = ApifyClient('YOUR_TOKEN')
run = client.actor('foxlabs/youtube-channel-videos-scraper').call(run_input={"channels": ["@mkbhd"], "maxVideosPerChannel": 30})
for row in client.dataset(run.default_dataset_id).iterate_items():
    if row['type'] == 'video':
        print(row['publishedDate'], row['views'], row['outlierScore'], row['title'])
```

Written for `apify-client` 3 for Python, where `call()` returns a run object; with version 2 it returns a dict — write `run['defaultDatasetId']` there.

**Automation (n8n / Zapier / Make):** schedule or webhook → run the Actor with your `channels` and `onlyNewVideos` → handle the JSON dataset → push new videos to a sheet, Slack, a CRM or a dashboard.

### 📊 Pricing

Pay-per-event. Every delivered row carries exactly one of three events, named by its `detailLevel`:

| Event | When | Price |
|---|---|---|
| `video` | the row was made from the channel's list and feed alone (`details: "list"`) | $0.0004 a row ($0.40 per 1,000) |
| `video-exact` | the video's exact figures were read (`details: "figures"`, the default) | $0.0006 a row ($0.60 per 1,000) |
| `video-detailed` | the video's own page was read (`details: "full"`) | $0.001 a row ($1.00 per 1,000) |

Plus Apify's run-start event ($0.00005 for a run with up to 1 GB of memory — the default — also for a run that delivers nothing; Apify counts this event per GB of run memory, so a run you start with more is charged more of them: we measured 256 MB and 1 GB, one event each). [Current pricing](https://apify.com/foxlabs/youtube-channel-videos-scraper).

**Not charged:** status rows, videos that did not pass your filters, and videos that could not be read. A video asked for with `full` whose page YouTube did not give is delivered with its exact figures and charged as `video-exact`.

**Spending limit.** Set a maximum cost per run in the run options. Before every row the Actor checks that it still fits; when the next row would not, it stops delivering, keeps what it has delivered, and says so in its status message.

### ❓ FAQ

**How far back does it go?** As far as the channel's own lists go: each list is followed until YouTube offers no further page. The longest list we followed had 4,641 videos. The lists can hold a few videos fewer than the count on the channel's About panel (1,852 against 1,856 for one channel we compared).

**Are the view counts exact?** With `details` set to `figures` or `full`, yes: they are the unrounded counts YouTube shows on each video's page. With `list`, the channel's latest 15 uploads have unrounded counts from the feed (a fresh upload for which the feed lags behind the list keeps the list's rounded figure) and older videos have the list's rounded ones; `viewsAreExact` tells you which.

**When do I need `details: "full"`?** For the number of comments, the description, hashtags and chapters. Views, likes and dates are the same in `figures`, which is several times faster and, in our measurements, not limited by YouTube.

**What is a good `minOutlierScore`?** 2 keeps the videos that did clearly better than usual, 5 the rare hits. The score is views against the median of the channel's latest videos of the same kind, so an old hit on a channel that has shrunk since scores very high.

**Why is `publishedAt` empty for older videos?** The Actor reads the time of day from the channel's feed, which holds the latest 15 uploads. Older videos get `publishedDate` from the video itself (`details: "figures"` or `"full"`).

**And why can it be empty for a channel's latest videos too?** Because YouTube's feed service does not always answer. On 2026-10-08 — by the run records of this series of Actors from about 01:25 UTC until some time between 06:47, when the last run with failing feeds ended, and 09:48, when all 40 feeds we asked for answered again — it answered "not found" or "server error" for channels that have a feed: of 40 feeds that had answered the day before, 8 answered a single request at 02:01 UTC, 14 at 02:25 and none at 02:58. Others describe the same fault of this endpoint as recurring, in windows of hours, since December 2025 (threads at n8n and Google's developer forum, an issue at Miniflux, a pull request at RSS-Bridge, as we read them on 2026-10-08); how often it happens we cannot say from two days. The Actor asks a feed that does not come up to five more times (that brought 25 of the 40 at 02:01 and 39 at 02:25, but only 2 at 02:58, when the service was down almost altogether; while a run's last channels bring next to no feeds it asks once more instead of five times). When it still does not come, the channel's videos are delivered all the same, and the status message says for how many channels the feed did not answer and what their latest uploads lack: with `full` only the time of day (`publishedAt`); with `figures` also their description, which that mode takes from the feed; with `list` also their likes and — unless they are under a day old — their date (`publishedDate`, `daysSincePublished`, `viewsPerDay`; a Short without a date cannot pass `publishedWithinDays`), and their view counts are the list's rounded ones (`viewsAreExact` says which).

**Why does a Short have no duration, and no date with `details: "list"`?** The Shorts list shows neither. `figures` and `full` give the date; YouTube publishes no duration for Shorts in the places this Actor reads.

**Can I monitor channels without getting the same videos again?** Yes. Turn on `onlyNewVideos` and give the list a `listName`. Delivered videos are remembered in a key-value store named `youtube-videos-<listName>` in your account. Each run looks at the first `maxVideosPerChannel` videos of each list and delivers the ones it has not delivered before. A run that is killed before it could save its list — a timeout, for instance — is made up for: the list's next run first reads back what that run delivered.

**Does it need a proxy or an API key?** No API key, and nothing to set up. Requests go out directly. A kind of request YouTube refuses — in our runs only video pages, never lists, feeds or figures — is moved to Apify Proxy by the Actor itself. Our tests ran on an account with 30 datacenter proxy addresses (read from the account on 2026-10-08); how the switch behaves on a plan with fewer we have not measured.

**Can I export to CSV / Excel / JSON?** Yes — directly from the Apify dataset.

### 🐛 Troubleshooting

- **A channel line came back as a status row** — the `error` column says why: not a channel address, no channel at that handle, the same channel as another line, or a channel without videos of the kinds you asked for.
- **"… videos did not pass your filters (…)"** — the run's status message (and, when nothing at all was delivered, the one status row in its dataset) counts the videos each filter removed, for example `(142 older than publishedWithinDays, 35 without a date at this detail level)`. Videos "without a date at this detail level" need `details: "figures"` to be dated.
- **"No new videos, no rows charged …"** — nothing that the list `listName` has not delivered before stood at the top of the lists (or nothing of it passed your filters).
- **"… videos came with exact figures only … because YouTube did not give their pages"** — the `full` limit described under *How it works*. The rows are marked `detailLevel: "figures"`; run those channels again later for the rest.
- **"The run stopped at maxVideos before it got to N of its M channels"** — the total limit was reached first. Raise `maxVideos`, or lower `maxVideosPerChannel` so the channels share the run.
- **"N lists stopped answering before their end"** — YouTube stopped answering for a list part-way; what it brought was delivered. Run those channels again.
- **"The feed of N channels did not answer: their latest uploads come without …"** — YouTube's feed service gave nothing for those channels although the Actor asked again (up to five more times; once more while the service is down for nearly every channel). Their videos are delivered; the message names what the latest 15 lack at your `details` level (see the FAQ). Run those channels again later for the rest.
- **"… another run of the list … is in progress, or was cut off in the last three minutes"** — two runs of the same `listName` would deliver the same videos; start the second when the first has finished.
- **"Stopped after reading N videos: the filters let too few through"** — a run reads at most 50 videos for every row it may deliver (`maxVideos`), and at least 300. Loosen the filters or raise `maxVideos`.

### 🚧 What it does not do

- No transcripts, no comments' text, no video or audio files.
- No tags and no category: YouTube serves them from an endpoint that turned one of our two tries away with a bot check; this Actor does not use it.
- Subscriber counts are YouTube's rounded figures. Comment counts of 1,000 and more are shortened by YouTube ("6.1K").
- No duration for Shorts: YouTube shows none in the places this Actor reads.
- YouTube can change its pages at any time; a number or date that cannot be read is `null`, never a guess.

### ⚖️ Is it legal to scrape YouTube channel data?

The Actor reads only what YouTube shows to every visitor who is not signed in: no login, no cookies, no CAPTCHA solving. Video titles, figures and descriptions are public information published by the channels themselves; a description can name people or contact details, so use the data for a lawful purpose under the law that applies to you and check that your use fits YouTube's terms. YouTube is a trademark of Google LLC; this Actor is not affiliated with, endorsed or sponsored by YouTube or Google.

### 🤝 Support & contact

- 🌐 **Website:** [data.foxlabs.com.tr](https://data.foxlabs.com.tr)
- 📧 **Email:** info@foxlabs.com.tr
- 🐛 **Issues:** open a ticket in the Actor's **Issues** tab
- 🧰 **More data actors:** [Fox Labs on Apify](https://apify.com/foxlabs)

### Changelog

#### 0.1.17 — 2026-10-08

- README only: the failure of YouTube's feed service on 2026-10-08 ended between 06:47 and 09:48 UTC (the README said "still at our last reading at 03:21"), and others describe the fault as recurring since December 2025. No change in what the Actor does.

#### 0.1.16 — 2026-10-08

- A channel's feed that answers "not found" or "server error" although the channel has videos is asked up to five more times; when it still does not come, the status message says what the channel's latest uploads lack at the run's `details` level.
- A run that delivers nothing now says "no rows charged" (it said "nothing charged"; the run-start event applies to every run). This README was corrected after an independent fact-check — the list is in the Changelog tab.

#### 0.1 — 2026-10-07 — first version

- A channel's Videos, Shorts and Live lists to their end, in the channel page's own orders.
- Three levels of detail per video: exact figures, the whole page, lists only — each with its own price.
- Outlier score against the channel's usual; filters by age, views and score; live, upcoming and members-only videos told apart.
- "Only new videos" lists for monitoring; runs that continue after a restart; a spending limit that is exact. The full list is in the Changelog tab.

# Changelog

This Actor's version history is a separate document: https://apify.com/foxlabs/youtube-channel-videos-scraper/changelog.md

# Actor input Schema

## `channels` (type: `array`):

One channel per line: a channel URL (https://www.youtube.com/@mkbhd), an @handle or a channel ID. URLs, handles and IDs can be mixed.

## `videoTypes` (type: `array`):

Which of the channel's lists to read: its Videos tab, its Shorts tab, its Live tab. Each kind is listed separately, up to the per-channel maximum.

## `maxVideosPerChannel` (type: `integer`):

How many videos to deliver for each channel, per kind of video. A channel's lists can be followed to their end: use a large number for a full archive.

## `maxVideos` (type: `integer`):

The run stops when this many rows have been delivered. You are charged only for delivered rows.

## `sort` (type: `string`):

The order of each list, as on the channel page: newest first, most viewed first, or oldest first.

## `details` (type: `string`):

"Exact figures" asks only for what a video's page keeps refreshing: the unrounded view count, the like count and the publish date. "Everything" reads each video's own page as well; YouTube limits how many pages one address may read in a row (about a thousand), after which the Actor moves to other addresses and, if none is left, delivers the remaining videos with exact figures only. "Lists and feed only" is the cheapest: about four requests a channel, for watching many channels.

## `publishedWithinDays` (type: `integer`):

Only videos published at most this many days ago. With the latest-first order the list is not read past this point. Exact with "Exact figures" and "Everything"; with "Lists and feed only" a video is judged by what the list shows ("1 month ago" passes 30 days) and a video without a date there — a Short outside the feed, a stream on air — is left out.

## `minViews` (type: `integer`):

Only videos with at least this many views. Leave empty (or 0) for no minimum.

## `minOutlierScore` (type: `string`):

Only videos that did at least this many times the channel's usual: 2 keeps videos with twice the median views of the channel's latest videos of the same kind, 5 keeps the rare hits. Decimals are fine (1.5).

## `onlyNewVideos` (type: `boolean`):

Remember delivered videos under the list name below and skip them in later runs. Schedule the same channels daily and each run brings only the videos it has not delivered before. With this on, "Maximum videos per channel" is how far down each list the Actor looks: of the first 30 (or whatever you set) it delivers the ones it has not delivered before, and it does not go further back.

## `listName` (type: `string`):

Name of the memory used by "Only videos not delivered before". Use a different name for each project. Letters, digits and hyphens (a hyphen not first or last), up to 40 characters; capitals are read as lower case.

## `proxyConfiguration` (type: `object`):

Nothing to change for normal use: requests go out directly, and the Actor switches to Apify Proxy by itself if YouTube refuses them. Turn a proxy on here only if you want every request to use it.

## Actor input object example

```json
{
  "channels": [
    "@mkbhd"
  ],
  "videoTypes": [
    "videos"
  ],
  "maxVideosPerChannel": 30,
  "maxVideos": 1000,
  "sort": "latest",
  "details": "figures",
  "onlyNewVideos": false,
  "listName": "default",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "channels": [
        "@mkbhd"
    ],
    "maxVideosPerChannel": 30,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("foxlabs/youtube-channel-videos-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "channels": ["@mkbhd"],
    "maxVideosPerChannel": 30,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("foxlabs/youtube-channel-videos-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "channels": [
    "@mkbhd"
  ],
  "maxVideosPerChannel": 30,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call foxlabs/youtube-channel-videos-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,foxlabs/youtube-channel-videos-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/SWiYbGET4F7968yaP/builds/eoR73ly0jd9dvqHIt/openapi.json
