# YouTube Channel Upload Monitor (`praise-most-high/youtube-channel-upload-monitor`) Actor

YouTube Channel Monitor that returns new uploads with title, description, publish date, view count, rating count and thumbnail. 22 typed fields per video. Watch many channels on a schedule and get only what is new. Export to JSON, CSV or Excel.

- **URL**: https://apify.com/praise-most-high/youtube-channel-upload-monitor.md
- **Developed by:** [angel nguyen](https://apify.com/praise-most-high) (community)
- **Categories:** Videos, Social media, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.00 / 1,000 video returneds

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

### What this Actor does

YouTube Channel Upload Monitor collects the newest uploads from the channels you name and hands
you each one as a typed record — the title, the full description, the publish date, the view count,
the rating count and the thumbnail. You give it a list of handles; on a schedule it returns only
what has appeared since it last ran.

![Actor icon](https://apify-image-uploads-prod.s3.us-east-1.amazonaws.com/0ezaunoiVKF5LFUup-actor-jg67ynWzwKtubeMkC-aEwkm4g0Vz-youtube-channel-upload-monitor.png)

It reads YouTube's own published upload feed at `https://www.youtube.com/feeds/videos.xml`. That is
a first-party syndication surface: the same document a feed reader subscribes to. There is no API
key, no quota to exhaust, and nothing behind a login.

### Key features

| Feature | Detail |
|---|---|
| Input | Handles like `@NASA`, channel URLs, `UC…` channel IDs or playlist IDs — mixed freely in one list |
| Fields per video | 22 typed fields, including the full description, view count, rating count and average |
| Resolution | A handle or custom URL is resolved to its canonical channel ID before the feed is read |
| Playlists | A playlist ID reads that playlist's feed instead of a channel's uploads |
| Incremental | `onlyNewSinceLastRun` remembers the video IDs it delivered per channel, so a schedule returns only new uploads |
| Filters | `minPublishedDate` and `minViewCount` drop videos before you are charged for them |
| Unresolvable channels | Reported as `unavailable` with a reason, and never charged for |
| Output | Flat rows, one per video — no nested blobs, exports cleanly to CSV and Excel |

### How it works

1. **Resolve every reference.** A `UC…` ID and a playlist ID address a feed directly. A handle, a
   custom URL or a `/user/` URL needs one page fetch, from which the **canonical** channel ID is
   read — not the first `UC…` string on the page, which belongs to a recommendation rail.
2. **Read the published feed.** One request per channel to YouTube's own `videos.xml`.
3. **Apply your filters.** Date floor, view floor and the since-last-run set are applied before
   anything is charged, so a video you filtered out is a video you did not pay for.
4. **Charge, then deliver.** Records are charged before they are pushed and delivery is truncated to
   the number actually charged, so the row count you receive and the row count you are billed for
   are the same number.

A run over ten channels typically finishes in a few seconds.

#### What the feed does and does not carry

**YouTube publishes the 15 most recent uploads on a channel's feed, and nothing older.** That is a
property of the feed, not a limit this Actor chose, and it is why `maxVideosPerChannel` caps at 15.
This is a monitor: point it at channels you want to keep watching and run it on a schedule. It is
not a way to download a channel's back catalogue, and it does not claim to be.

The feed also carries no comment thread, no subscriber count and no video duration, so none of
those are in the schema. A field this Actor does not serve is a field it does not advertise.

### Input

The minimum input is one channel. Everything else has a working default.

```json
{
  "channels": ["@NASA", "UCBR8-60-B28hp2BmDPdntcQ", "https://www.youtube.com/@Google"],
  "maxVideosPerChannel": 15,
  "minPublishedDate": "2026-08-01",
  "minViewCount": 0,
  "onlyNewSinceLastRun": true,
  "includeChannelRecord": true
}
```

| Field | Type | Default | Meaning |
|---|---|---|---|
| `channels` | array of string | — | Handles, channel URLs, `UC…` IDs or playlist IDs. Required. |
| `maxVideosPerChannel` | integer | `15` | Cap per channel. The feed itself holds 15, so this only lowers it. |
| `minPublishedDate` | string | — | ISO date. Older videos are neither returned nor charged. |
| `minViewCount` | integer | — | Skip videos below this. A video with no counter at all is kept. |
| `onlyNewSinceLastRun` | boolean | `false` | Remembers delivered video IDs per channel and skips them next run. |
| `includeChannelRecord` | boolean | `true` | Adds one summary row per channel alongside the videos. |

### Output

Every run writes one row per video to the default dataset. The record below is a real row from a
real run against `@YouTube`, copied unchanged:

```json
{
  "recordType": "video",
  "videoId": "fFScGqpkth8",
  "title": "don't talk to us til we've had our pumpkin spice latte",
  "url": "https://www.youtube.com/shorts/fFScGqpkth8",
  "channelId": "UCBR8-60-B28hp2BmDPdntcQ",
  "channelTitle": "YouTube",
  "channelUrl": "https://www.youtube.com/channel/UCBR8-60-B28hp2BmDPdntcQ",
  "publishedAt": "2026-08-29T20:04:22+00:00",
  "updatedAt": "2026-08-30T01:22:25+00:00",
  "description": null,
  "descriptionLength": 0,
  "thumbnailUrl": "https://i3.ytimg.com/vi/fFScGqpkth8/hqdefault.jpg",
  "thumbnailWidth": 480,
  "thumbnailHeight": 360,
  "viewCount": 15763,
  "ratingCount": 400,
  "ratingAverage": 5,
  "hashtagsInDescription": [],
  "linksInDescription": [],
  "requestedAs": "UCBR8-60-B28hp2BmDPdntcQ",
  "feedUrl": "https://www.youtube.com/feeds/videos.xml?channel_id=UCBR8-60-B28hp2BmDPdntcQ",
  "scrapedAt": "2026-08-30T04:41:27.101Z"
}
```

`description` is `null` there because that upload genuinely has no description, and `null` is what
that means. A field the feed does not carry is never rendered as `0` or `""`; absent and empty are
different answers and they stay different.

#### The channel summary record

With `includeChannelRecord` left on, each channel also produces one row of type `channel`:

```json
{
  "recordType": "channel",
  "channelId": "UCLA_DiR1FfKNvjuUpBHmylQ",
  "channelTitle": "NASA",
  "channelUrl": "https://www.youtube.com/channel/UCLA_DiR1FfKNvjuUpBHmylQ",
  "channelPublishedAt": "2008-06-03T18:44:30+00:00",
  "requestedAs": "@NASA",
  "feedUrl": "https://www.youtube.com/feeds/videos.xml?channel_id=UCLA_DiR1FfKNvjuUpBHmylQ",
  "videosInFeed": 15,
  "scrapedAt": "2026-08-30T04:41:27.101Z"
}
```

`requestedAs` carries the string you supplied, so a row can always be traced back to the entry in
your input that produced it. The dataset exports to JSON, CSV, Excel and XML from the Storage tab,
and the same rows are readable through the dataset API.

### Pricing

This Actor is priced per event, so you pay for what it delivers rather than for how long it runs.

| Event | Price | Charged when |
|---|---|---|
| `video-returned` | $0.006 | Once per video record delivered to the dataset |
| `channel-resolved` | $0.002 | Once per channel or playlist whose feed was successfully read |

There is no per-run start fee and no monthly minimum. Watching 20 channels with nothing new to
report costs $0.04 — the channel reads and nothing else. A first run over 20 channels that returns
a full feed each costs $0.04 plus 300 videos at $0.006, so $1.84.

**How that price was set.** It is anchored on what comparable YouTube monitors on this Store charge
per delivered row, which runs from $0.0032 to $0.015, and it sits deliberately in the upper half of
that band because the record is wider: 22 typed fields including the full description text, the
rating count and average, and the hashtags and links parsed out of the description. There is room
to move the price down and none is needed to move it up.

Charges are applied before records are handed over, and delivery is truncated to whatever your own
budget cap allowed, so you are never billed for a row you did not receive.

### FAQ

**Do I need a YouTube API key or a Google account?**
No. The upload feed is public and needs no credential. This Actor holds none and asks you for none.

**Why only 15 videos per channel?**
Because that is what YouTube publishes on the feed. Asking for more would mean claiming a back
catalogue this surface does not carry.

**Can it watch a playlist?**
Yes. Pass a playlist ID and it reads that playlist's feed instead of the channel's uploads.

**Why did a channel come back as unavailable?**
Either the handle does not resolve to a channel, or YouTube did not serve its feed. Both are
reported with the reason, and neither is charged for. A channel that genuinely has no uploads
resolves fine and returns zero videos, which is a different answer.

**Are the view counts live?**
They are exactly what the feed said at the moment it was read, and `scrapedAt` records that moment.
The feed lags the watch page slightly; this Actor does not smooth over the difference.

**Can I use it to detect new uploads without polling the whole feed?**
That is what `onlyNewSinceLastRun` does. It keeps the delivered video IDs per channel in a
key-value store inside your own account and skips them next time, so a scheduled run returns only
genuinely new uploads — and charges only for those.

**Does it get transcripts, comments or subscriber counts?**
No. None of them are on this feed, so none of them are in the schema.

### Limits and troubleshooting

- **`maxVideosPerChannel` is a cap, not a target.** A channel with fewer uploads on its feed returns
  fewer rows, and you are charged for the rows you got.
- **Handles cost one extra request.** A `UC…` channel ID goes straight to the feed; a handle needs a
  page lookup first. If you are watching the same channels repeatedly, store their IDs.
- **Rate limiting.** A `429` or `5xx` is retried three times with a growing pause. A feed that stays
  unreachable is reported as unavailable, never treated as an empty channel.
- **Shorts are uploads too.** They appear on the feed and are returned like any other video, with a
  `/shorts/` URL. There is no flag on the feed distinguishing them beyond that URL.

### Integrations and API

The Actor is callable from anywhere the Apify API reaches.

```bash
curl -X POST "https://api.apify.com/v2/acts/praise-most-high~youtube-channel-upload-monitor/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"channels":["@NASA"],"onlyNewSinceLastRun":true}'
```

- **Scheduling** — run it hourly or daily from the Apify scheduler with `onlyNewSinceLastRun` on and
  the dataset only ever grows by what was actually published.
- **Webhooks** — fire on `ACTOR.RUN.SUCCEEDED` to push new uploads straight into your own store.
- **Python and JavaScript SDKs** — both call it the same way; see the
  [Apify client documentation](https://docs.apify.com/api/client/js/).
- **MCP** — reachable as a tool from any MCP-speaking agent through Apify's own MCP server, with
  `LIMITED_PERMISSIONS` so it can be paid for per call.

### Legal and data handling

**Unofficial.** This Actor is not affiliated with, endorsed by, or sponsored by YouTube LLC or
Google LLC. "YouTube" is a trademark of its owner and is used here only to identify the service
being read.

The Actor reads a public syndication feed that YouTube itself publishes for the purpose of being
read by feed clients. It collects no personal data, no viewer information and nothing behind an
authentication boundary, and it stores no credentials because it needs none.

Whether your own use of the output is permitted is a question about your jurisdiction and your
purpose, and it is yours to answer — this Actor makes no representation about it. YouTube's own
[Terms of Service](https://www.youtube.com/t/terms) govern your relationship with YouTube.

### Support and feedback

Open an issue on the Actor's Issues tab. Bug reports that include the run ID and the input that
produced the problem are answered fastest. Feature requests are read, and they are the main source
of what gets built next.

# Actor input Schema

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

Handles such as @NASA, channel URLs, UC… channel IDs, or playlist IDs, mixed freely in one list. A reference that does not resolve is reported as unavailable and is never charged for.

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

Hard cap on videos returned per channel. YouTube's feed publishes the 15 most recent uploads, so values above 15 return whatever the feed actually holds and cost less than the cap.

## `minPublishedDate` (type: `string`):

Optional ISO date such as 2026-01-01. Videos published before it are neither returned nor charged.

## `minViewCount` (type: `integer`):

Skip videos below this view count. Videos whose feed entry carries no view counter are kept, because absent and zero are different facts.

## `onlyNewSinceLastRun` (type: `boolean`):

Remembers the video IDs it delivered per channel in a key-value store in your own account and skips them next time. This is what makes it a monitor rather than a scraper.

## `includeChannelRecord` (type: `boolean`):

Writes one extra record per channel with the resolved channel ID, title, URL and the date the channel was created. Turn it off for videos only.

## Actor input object example

```json
{
  "channels": [
    "@NASA",
    "@Google"
  ],
  "maxVideosPerChannel": 15,
  "onlyNewSinceLastRun": false,
  "includeChannelRecord": true
}
```

# Actor output Schema

## `videos` (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": [
        "@NASA",
        "@Google"
    ],
    "maxVideosPerChannel": 15,
    "minPublishedDate": "",
    "minViewCount": 0,
    "onlyNewSinceLastRun": false,
    "includeChannelRecord": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("praise-most-high/youtube-channel-upload-monitor").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": [
        "@NASA",
        "@Google",
    ],
    "maxVideosPerChannel": 15,
    "minPublishedDate": "",
    "minViewCount": 0,
    "onlyNewSinceLastRun": False,
    "includeChannelRecord": True,
}

# Run the Actor and wait for it to finish
run = client.actor("praise-most-high/youtube-channel-upload-monitor").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": [
    "@NASA",
    "@Google"
  ],
  "maxVideosPerChannel": 15,
  "minPublishedDate": "",
  "minViewCount": 0,
  "onlyNewSinceLastRun": false,
  "includeChannelRecord": true
}' |
apify call praise-most-high/youtube-channel-upload-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,praise-most-high/youtube-channel-upload-monitor"
        }
    }
}

```

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/jg67ynWzwKtubeMkC/builds/ftFXuRAVBiHtgav00/openapi.json
