# ViewStats Scraper - YouTube Channel Analytics & Growth Data (`abotapi/viewstats-channel-analytics`) Actor

Scrape YouTube channel analytics from ViewStats. Look up channels by handle, URL, or keyword search. Returns subscribers, views, rankings, growth from weekly to all-time, revenue estimates, historical time series, longs vs shorts, featured video, and similar channels.

- **URL**: https://apify.com/abotapi/viewstats-channel-analytics.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Social media, Videos, Developer tools
- **Features:** Limited permissions
- **Stats:** 36 total users, 5 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 channel results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## ViewStats Scraper: YouTube Channel Analytics, Growth & Revenue Estimates

ViewStats Scraper turns ViewStats into a clean YouTube channel analytics API. Get subscriber and view counts, global, country and category rankings, week to all time growth with revenue estimates, daily and monthly averages, upload frequency, longs vs shorts splits, the featured video, similar channels, and a full historical time series for any channel. Search by keyword or paste a list of handles or URLs, then export to JSON, CSV or Excel, or pull the results straight into your app through the API.

### Why This Scraper?

- **Two ways to find channels.** Paste a list of handles or URLs, or search by keyword and let the actor resolve the matching channels for you.
- **A full analytics profile per channel.** Subscriber and view counts, global, country and category rankings, week to all time growth with revenue estimates, daily and monthly averages, and upload frequency.
- **Historical time series.** Daily or monthly data points over a 28 day, 90 day, 365 day or all time window, covering subscribers, views, revenue estimates and upload events.
- **Content and format context.** The longs vs shorts split and the current featured video, so you can see what format is working for a channel right now.
- **Similar channels on demand.** Pull related channels with their own subscriber, view and video counts plus a similarity score.
- **One flat surcharge, not three.** Profile details, similar channels and the time series are independent toggles, but turning on more than one adds only a single detail enrichment charge per channel, never a multiple.
- **Built for schedules.** Incremental mode returns only new, changed and reappeared channels on recurring runs, and Resume continues one interrupted pull without paying twice.

### Use Cases

- **Creator and MCN research:** benchmark subscriber growth, revenue ranges and upload cadence across a list of competitor channels.
- **Sponsorship and influencer marketing:** check a channel's real reach, country ranking and audience size before pitching or paying for a placement.
- **Market and niche discovery:** search a keyword to resolve a fresh list of active channels in that niche, then rank them by subscribers or views.
- **Growth tracking and reporting:** schedule the actor daily or weekly and get a feed of only the channels that changed.
- **Content and competitive analysis:** pull each channel's featured video and longs vs shorts split to see what format is working for them right now.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live channel.

| Field | Example |
| --- | --- |
| `channelId` / `id` | `"UC00000000000000000000"` (`id` is an alias with the same value) |
| `handle` | `"samplecreator"` |
| `displayName` | `"Sample Creator"` |
| `url` | `"https://www.viewstats.com/@samplecreator/channelytics"` |
| `youtubeUrl` | `"https://www.youtube.com/@samplecreator"` |
| `verified` | `true` |
| `country` | `"US"` |
| `subscriberCount` / `viewCount` / `videoCount` | `1000000` / `500000000` / `250` |
| `totalFollowing` | `12` |
| `globalSubscribersRanking` / `globalViewsRanking` | `1500` / `1800` |
| `countrySubscriberRanking` / `categorySubscriberRanking` | `300` / `120` |
| `vpv90` | `2500000` |
| `avatarUrl` / `bannerUrl` | channel avatar and banner image URLs |
| `description` / `dateCreated` / `viewstatsRanking` | profile detail fields (when Include profile details is on) |
| `growth` | period growth object: week, month, quarter, year and all time, with revenue estimates (when enabled) |
| `estimatedRevenueLowUsdMonthly` / `estimatedRevenueHighUsdMonthly` | `10000` / `30000` (when enabled) |
| `averages` / `uploadFrequencyPerMonth` | daily, weekly and monthly averages object, and `8` (when enabled) |
| `longsAndShorts` | longs vs shorts split object (when enabled) |
| `featuredVideo` | featured video object: title, views, likes, duration (when enabled) |
| `similarChannels` | array of related channels with their own counts and a similarity score (when enabled) |
| `stats7` / `events` | last 7 daily data points with revenue estimates, and flattened upload events (id, date, type, viewCount, title) (when time-series is enabled) |
| `shortVideos` / `longVideos` | recent shorts and long-form videos (id, title, views, likes, comments, duration, outlierScore, averageViews, uploadDate) (when profile details are enabled) |
| `statsTimeSeries` | array of dated data points: subscribers, views, revenue estimates, upload events (when enabled) |
| `scrapedAt` | `"2026-01-01T00:00:00.000Z"` |
| `changeType` / `changedFields` / `firstSeenAt` / `lastSeenAt` | `"UPDATED"` / `["subscriberCount"]` / timestamps, incremental mode only |

Every record also carries two auxiliary counters not shown above, `recentTests` and `totalTests` (the channel's recent and lifetime thumbnail/title test counts as reported by ViewStats). They come from the core channel lookup, so they appear whatever the detail toggles are set to, and can be `null`.

### How to Use

1. Pick a **mode**: `channels` (paste handles or URLs) or `search` (resolve channels from keywords).
2. Fill in the channels or search terms for that mode. If you leave **Channels** empty in `channels` mode, the actor scrapes its default list (a change from earlier versions, where an empty field returned nothing useful; set your own channels to avoid it): four ViewStats channels, one per accepted URL shape: channel page (`https://www.viewstats.com/@MrBeast`), analytics page (`https://www.viewstats.com/@mkbhd/channelytics`), non-www host (`https://viewstats.com/@PewDiePie`) and videos tab (`https://www.viewstats.com/@veritasium/videos`), each a different channel and one result. Handles (`@MrBeast`, `MrBeast`) and YouTube channel URLs work too. Then switch on the detail toggles you need: profile details, similar channels, or the time series.
3. Set **Max channels to output** to control run size and cost, then click **Start**.
4. Download the dataset as JSON, CSV or Excel, or read it through the API.

**One channel, everything on:**

```json
{
  "mode": "channels",
  "channels": ["@MrBeast"],
  "includeProfileDetails": true,
  "includeSimilarChannels": true,
  "includeStatsTimeSeries": true,
  "statsRange": "365",
  "statsGroupBy": "daily"
}
```

**Several channels by handle and URL, core fields only (cheapest):**

```json
{
  "mode": "channels",
  "channels": ["@MrBeast", "https://www.youtube.com/@mkbhd", "tseries"],
  "includeProfileDetails": false,
  "maxItems": 3
}
```

**Keyword search, resolve the top matches:**

```json
{
  "mode": "search",
  "searchTerms": ["tech reviews", "mrbeast"],
  "maxChannelsPerSearch": 10
}
```

**Daily monitoring of the same channel list, only what changed:**

```json
{
  "mode": "channels",
  "channels": ["@MrBeast", "@mkbhd"],
  "includeProfileDetails": true,
  "incrementalMode": true
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/viewstats-channel-analytics").call(run_input={"mode": "channels", "channels": ["@MrBeast"]})
for channel in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(channel["displayName"], channel["subscriberCount"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/viewstats-channel-analytics').call({ mode: 'channels', channels: ['@MrBeast'] });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### How the detail-enrichment surcharge works

Profile details, similar channels and the time series each fetch their own extra endpoints, but they share one charge: any channel that has at least one of the three toggles on is billed exactly one flat detail-enrichment surcharge, whether one toggle is on or all three are. A channel suppressed by incremental mode (see below) is never charged for enrichment either, even though its detail endpoints still had to be fetched to classify it.

#### Resume and recurring updates

- **Resume** (`resumeFromRunId`) continues one interrupted run: paste its run or dataset ID and the actor skips every channel it already collected there, so you don't pay twice. The skip happens before the Max channels cap is applied, so resume frees the budget for genuinely new work.
- **Incremental mode** (`incrementalMode`) is for scheduled runs over the same channel list or search. State is kept per scope (mode, channel list or search terms, the three detail toggles, and the time series range and granularity), never keyed on `maxItems` or `maxChannelsPerSearch`, so tuning the output size alone never resets tracking. Each channel is classified `NEW`, `UPDATED` (with `changedFields`), `UNCHANGED` (suppressed and not billed unless `emitUnchanged` is on), `REAPPEARED`, or `EXPIRED` (only after a run that scanned the whole tracked scope, and only with `emitExpired` on). A field flipping to or from missing or null, or any change inside `similarChannels` (the whole list is excluded from change detection), or the live view, like and comment counters (and outlier score and average views) of the featured video, the recent short and long videos and the upload events (including the upload events nested inside `stats7` and `statsTimeSeries`) ticking, never counts as a change by itself; only a genuine value to value change elsewhere, or the videos themselves changing (a new, removed or different video, or an edit to a title or description), does. `ignoreFieldsForChanges` lists extra top-level fields to leave out of change detection (they are still returned); it applies to the stored baseline too, so editing it causes no one-time wave of `UPDATED` rows. `stateKey` names or shares the stored state; leave it empty to let the actor derive one automatically.
- Turning on incremental mode together with `resumeFromRunId`, when no incremental baseline exists yet for that scope, bootstraps the new baseline from the resume dataset, so those channels are not reported as `NEW` again. On the next scheduled run they come back once as `UPDATED` while their full snapshot is recorded, and after that only real changes are returned.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape, the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each record, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `channels` | Pick how to find channels: `channels` for a list of handles or URLs, `search` for keywords. |
| `channels` | array | the 4 sample URLs (prefill: the same 4) | Channels mode. One per line: a `@handle`, a bare handle, a ViewStats URL, or a YouTube URL (`/@handle` or `/channel/UC...`). |
| `startUrls` | array | the 4 sample URLs (same as `channels`) | Alias of `channels` (same accepted shapes), merged with it. An untouched sample list in either field is dropped when the other holds your own entries. Sending only `searchTerms` (with the untouched samples) runs search mode on your terms. |
| `searchTerms` | array | none (prefill: `["mrbeast"]`) | Search mode. Keywords to resolve into channels. |
| `maxChannelsPerSearch` | integer | `10` | Search mode. Max channels resolved per keyword. |
| `includeProfileDetails` | boolean | `true` | Adds description, creation date, rankings, period growth with revenue estimates, averages, upload frequency, longs vs shorts, the featured video, and the recent short and long videos (`shortVideos`, `longVideos`). |
| `includeSimilarChannels` | boolean | `false` | Adds related channels with counts and a similarity score. |
| `includeStatsTimeSeries` | boolean | `false` | Adds the historical data point series (subscribers, views, videos and revenue estimates per point, plus upload events), the 7-day daily window (`stats7`) and the flattened upload events list (`events`). |
| `statsRange` | string | `365` | Time series window: `28`, `90`, `365`, or `alltime`. Used only when the time series is on. |
| `statsGroupBy` | string | `monthly` | Time series granularity: `daily` or `monthly`. Used only when the time series is on. |
| `maxItems` | integer | `50` | Hard cap on the number of channel records written. `0` means unlimited. |
| `proxy` | object | `{ "useApifyProxy": true }` | Connection settings. |
| `residentialCountries` | array | `["US", "GB", "DE", "CA", "FR"]` | Countries rotated through when residential proxy is used as a fallback. |
| `maxResidentialRequests` | integer | `200` | Cap on residential requests per run, to control cost. `0` disables residential entirely. |
| `backupProxyUrl` | string | none | Optional last-resort connection gateway URL, used only as an escalation when the Apify connections are unavailable (for example on a plan without proxy access) or keep failing. Not used when you supply your own proxy URLs. Leave blank to use the built-in default. |
| `resumeFromRunId` | string | none | Paste a previous run ID or dataset ID to continue a large channel or search crawl without re-fetching (or re-charging for) channels already collected there. |
| `incrementalMode` | boolean | `false` | Turn on for daily or recurring monitoring of the same channels or search. Later runs return only `NEW`, `UPDATED`, and `REAPPEARED` channels. |
| `stateKey` | string | none | Optional. Names an incremental-mode monitoring campaign, or leave empty to derive one automatically from the channel list or search and the detail toggles. |
| `emitUnchanged` | boolean | `false` | Incremental mode only. Also return (and bill for) channels with no change since the last run, marked `UNCHANGED`. |
| `emitExpired` | boolean | `false` | Incremental mode only. Also return (and bill for) channels that disappeared since the last run, marked `EXPIRED`. Only produced after a complete, uncapped, non-resumed scan. |
| `ignoreFieldsForChanges` | array | none (prefill: `[]`) | Incremental mode only. Top-level field names (for example `subscriberCount`) that never count as a change and are left out of `changedFields`; they are still returned in every row. On top of this, the live counters of the featured video, the recent short and long videos and the upload events (including those nested inside `stats7` and `statsTimeSeries`) never count as a change. |
| `mcpConnectors` | array | none | Optional: send a summary of each record to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | none | Notion connector only: page under which item pages are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. Does not affect the dataset. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live channel.

```json
{
  "channelId": "UC00000000000000000000",
  "handle": "samplecreator",
  "displayName": "Sample Creator",
  "url": "https://www.viewstats.com/@samplecreator/channelytics",
  "youtubeUrl": "https://www.youtube.com/@samplecreator",
  "verified": true,
  "country": "US",
  "subscriberCount": 1000000,
  "viewCount": 500000000,
  "videoCount": 250,
  "totalFollowing": 12,
  "globalSubscribersRanking": 1500,
  "globalViewsRanking": 1800,
  "countrySubscriberRanking": 300,
  "categorySubscriberRanking": 120,
  "viewstatsRanking": 2000,
  "vpv90": 2500000,
  "recentTests": 3,
  "totalTests": 40,
  "avatarUrl": "https://yt3.ggpht.com/0000000000000000=s800-c-k-c0x00ffffff-no-rj",
  "bannerUrl": "https://yt3.googleusercontent.com/00000000000000000000",
  "description": "Sample channel description text appears here.",
  "dateCreated": "2015-01-01T00:00:00.000Z",
  "estimatedRevenueLowUsdMonthly": 10000,
  "estimatedRevenueHighUsdMonthly": 30000,
  "uploadFrequencyPerMonth": 8,
  "growth": {
    "weekly": { "views": 20000000, "subscribers": 50000, "estimatedRevenueLowUsd": 2000, "estimatedRevenueHighUsd": 6000 },
    "monthly": { "views": 90000000, "subscribers": 200000, "estimatedRevenueLowUsd": 10000, "estimatedRevenueHighUsd": 30000 }
  },
  "averages": {
    "daily": { "viewsAverage": 3000000, "subsAverage": 7000, "estimatedRevenueLowUsd": 1000, "estimatedRevenueHighUsd": 3000 }
  },
  "similarChannels": [
    { "channel": { "handle": "anothercreator", "displayName": "Another Creator" }, "subscriberCount": 900000, "viewCount": 400000000, "videoCount": 200, "similarity": 0.82 }
  ],
  "scrapedAt": "2026-01-01T00:00:00.000Z"
}
```

### Plan Requirement

No residential proxy and no paid proxy add-on is required. In testing, ViewStats channel pages loaded the same real content through both the standard (datacenter) Apify connection and residential connections, and no blocking was observed, so the default connection is used first. Residential proxy is an automatic, bounded fallback (`maxResidentialRequests`, default 200), and a backup connection is the last resort. A page that does not exist is answered by the site with a normal "Page Not Found" page, which the actor treats as an unrecognized channel, not as a connection problem. For the lowest cost, keep **Include profile details** off when you only need the core counts, and turn on the time series only when you need history.

On an Apify plan without proxy access, the run log shows this notice and continues on the backup connection when it can:

> The selected connection is unavailable to this run.
> Apify free plans do not include residential proxies.
> Continuing on a backup connection; some pages may still be unavailable.
> For residential access, upgrade at https://apify.com/pricing or supply your own proxy URLs.

When pages are then served over the backup connection, the log says "Served over the backup connection (the default connection was unavailable)."

Requests start on the standard Apify connection, then escalate to residential (within `maxResidentialRequests`), then to the backup connection.

### FAQ

#### How much does it cost?

You pay per channel record returned, plus one flat detail-enrichment surcharge per channel when any of the three detail toggles (profile details, similar channels, time series) is on. Turning on more than one of those toggles does not multiply the surcharge. The **Pricing** tab shows the current rates. Use **Max channels to output** to cap the cost of any run.

#### Is it legal to scrape ViewStats?

This actor collects only publicly available channel analytics that ViewStats itself computes from YouTube's public data. You are responsible for how you use it: follow ViewStats' and YouTube's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution.

#### Can I get only new or changed channels on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Each run then returns only new, updated and reappeared channels, and unchanged ones are not billed unless you also turn on **Emit unchanged**.

#### What's the difference between Resume and Incremental mode?

Resume continues **one specific** interrupted run: paste its run or dataset ID and this run skips every channel it already collected there. Incremental mode is for scheduling this same channel list or search again and again; the actor keeps its own baseline and classifies every channel as `NEW`, `UPDATED`, `UNCHANGED`, `REAPPEARED`, or `EXPIRED`. Combining both, with no existing incremental baseline yet, bootstraps the monitoring baseline from the resume dataset instead of collecting everything from scratch.

#### Why did my run fail instead of returning an empty dataset?

A run that could not load a single page ends as FAILED with one of three messages, rather than quietly returning nothing:

- "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required: https://apify.com/pricing. If you entered your own proxy URLs, check that their credentials are valid."
- "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested ViewStats pages. Upgrade at https://apify.com/pricing or supply your own proxy URLs, then try again."
- "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later."

A run also stops with a clear message if the `resumeFromRunId` you provided cannot be read, or if incremental mode already has saved state for this scope while `resumeFromRunId` is also set (remove one of the two, or set a different **State key**, to bootstrap a separate campaign). If the data source cannot be reached for an individual channel, that channel is skipped with a warning, so check the run log if the dataset is smaller than expected. A channel reference the actor cannot recognize is skipped the same way.

#### Which output views are there?

The dataset has three views: **Channel overview** (core counts and rankings), **Channel detail** (growth, averages, rankings and the detail fields) and **Changes** (incremental mode: `changeType`, `changedFields`, first and last seen).

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push a summary of each channel into Notion, Linear or Airtable.

### 🔗 Want more social media data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>📱 <a href="https://apify.com/abotapi/universal-media-extractor"><b>Universal Media Extractor</b></a><br>Extract videos, audio, and metadata from 1000+ websites including YouTube, TikTok...</td><td>📱 <a href="https://apify.com/abotapi/tiktok-scraper"><b>TikTok Profile, Hashtag, Search, Video &amp; Trending Scraper</b></a><br>Scrape TikTok without login. Extract profiles, bios, follower stats and videos; search by...</td></tr>
<tr><td>🎬 <a href="https://apify.com/abotapi/twitch-scraper"><b>Twitch ALL IN ONE URL</b></a><br>From $1/1K. Scrape Twitch data at scale, including channels, live streams, clips, VODs...</td><td>🧩 <a href="https://apify.com/abotapi/dzen-ru-scraper"><b>Dzen.ru Scraper</b></a><br>Scrape Dzen.ru (ex Yandex.Zen): articles, videos and channels. Full article text with...</td></tr>
<tr><td>🎬 <a href="https://apify.com/abotapi/youtube-transcript-scraper"><b>YouTube Transcript &amp; Subtitle Scraper</b></a><br>Extract transcripts and subtitles from YouTube videos in bulk using video, playlist...</td><td>📱 <a href="https://apify.com/abotapi/tiktok-live-recorder"><b>Tiktok Live Recorder</b></a><br>Record TikTok live streams to MP4 with full metadata, all stream quality URLs, and...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/viewstats-channel-analytics/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

## `mode` (type: `string`):

Pick how to find channels. 'channels' takes a list of @handles or channel URLs. 'search' takes keywords and resolves matching channels.

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

Channels mode only. One per line: a @handle (e.g. @MrBeast), a bare handle (MrBeast), a ViewStats URL (https://www.viewstats.com/@MrBeast), or a YouTube URL (https://youtube.com/@MrBeast or https://youtube.com/channel/UC...).

## `startUrls` (type: `array`):

Channels mode only. Alias of 'Channels': accepts the same @handles, bare handles, ViewStats URLs and YouTube URLs. Entries are merged with 'Channels' and de-duplicated.

## `searchTerms` (type: `array`):

Search mode only. Keywords to look up. Each term resolves up to 'Max channels per search term' matching channels.

## `maxChannelsPerSearch` (type: `integer`):

Search mode only. Cap on how many channels each keyword resolves.

## `includeProfileDetails` (type: `boolean`):

Adds description, account creation date, channel-wide rankings, period growth (week/month/quarter/year/all-time) with revenue estimates, daily/weekly/monthly averages, upload frequency, longs-vs-shorts split, the featured video, and the recent short and long videos (shortVideos, longVideos). Costs a few extra requests per channel (including one for the recent videos).

## `includeSimilarChannels` (type: `boolean`):

Adds the list of similar/related channels with their subscriber, view and video counts plus a similarity score. One extra request per channel.

## `includeStatsTimeSeries` (type: `boolean`):

Adds the historical stats series (subscribers, views, videos, revenue estimates per data point, plus upload events), a 7-day daily window (stats7) and a flattened upload events list (events). Two extra requests per channel.

## `statsRange` (type: `string`):

Window for the time-series (only used when the time-series is enabled).

## `statsGroupBy` (type: `string`):

Bucket size for the time-series (only used when the time-series is enabled).

## `maxItems` (type: `integer`):

Hard cap on the number of channel records written. 0 means unlimited.

## `proxy` (type: `object`):

The default works on every Apify plan, including the free plan, and keeps cost low. Residential proxy is supported as an optional fallback for extra resilience.

## `residentialCountries` (type: `array`):

Country codes rotated through when residential proxy is used as a fallback. Leave default unless you have a reason to pin specific regions.

## `maxResidentialRequests` (type: `integer`):

Caps how many requests may use residential proxy in one run, to control cost. 0 disables residential entirely.

## `backupProxyUrl` (type: `string`):

Optional. A full proxy URL used as a last-resort failover when Apify proxy is unavailable (for example on a restricted plan). Leave blank to use the built-in default.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a large channel/search crawl without returning or charging for channels already collected there. Use this after an interrupted run, or when continuing a large channel list pull in another run. For recurring daily monitoring of the same channels/search, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring. The first run returns all matching channels as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED channels. Turn on "Emit unchanged" or "Emit expired" only when you also want those channels returned (and billed). State is kept separately for each channel list/search and detail-toggle setup; use State key when you want to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from the channel list/search and detail settings — different campaigns then never mix state with each other.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return channels that have not changed since the last run, marked UNCHANGED. This returns — and bills — extra rows you already have, so leave it off unless you specifically want the full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return channels that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked channels/search — not when Max channels output cap or the search cap limited it, or when Resume was used. This returns — and bills — extra synthetic rows, so leave it off unless you need expiry tracking.

## `ignoreFieldsForChanges` (type: `array`):

Optional. Top-level field names to exclude from UPDATED change detection and from changedFields, in addition to the built-in handling (scrapedAt, the similarChannels list and the live view, like and comment counters of the featured video, the recent short and long videos and the upload events, including those nested inside stats7 and statsTimeSeries, already never trigger a false UPDATED). Use it when another field, e.g. subscriberCount, is too noisy for your schedule. Ignored fields are still returned in every row. Changing this list never causes a one-time wave of UPDATED rows.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "channels",
  "channels": [
    "https://www.viewstats.com/@MrBeast",
    "https://www.viewstats.com/@mkbhd/channelytics",
    "https://viewstats.com/@PewDiePie",
    "https://www.viewstats.com/@veritasium/videos"
  ],
  "startUrls": [
    "https://www.viewstats.com/@MrBeast",
    "https://www.viewstats.com/@mkbhd/channelytics",
    "https://viewstats.com/@PewDiePie",
    "https://www.viewstats.com/@veritasium/videos"
  ],
  "searchTerms": [
    "mrbeast"
  ],
  "maxChannelsPerSearch": 10,
  "includeProfileDetails": true,
  "includeSimilarChannels": false,
  "includeStatsTimeSeries": false,
  "statsRange": "365",
  "statsGroupBy": "monthly",
  "maxItems": 50,
  "proxy": {
    "useApifyProxy": true
  },
  "residentialCountries": [
    "US",
    "GB",
    "DE",
    "CA",
    "FR"
  ],
  "maxResidentialRequests": 200,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `detail` (type: `string`):

No description

## `changes` (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 = {
    "mode": "channels",
    "channels": [
        "https://www.viewstats.com/@MrBeast",
        "https://www.viewstats.com/@mkbhd/channelytics",
        "https://viewstats.com/@PewDiePie",
        "https://www.viewstats.com/@veritasium/videos"
    ],
    "startUrls": [
        "https://www.viewstats.com/@MrBeast",
        "https://www.viewstats.com/@mkbhd/channelytics",
        "https://viewstats.com/@PewDiePie",
        "https://www.viewstats.com/@veritasium/videos"
    ],
    "searchTerms": [
        "mrbeast"
    ],
    "proxy": {
        "useApifyProxy": true
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/viewstats-channel-analytics").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 = {
    "mode": "channels",
    "channels": [
        "https://www.viewstats.com/@MrBeast",
        "https://www.viewstats.com/@mkbhd/channelytics",
        "https://viewstats.com/@PewDiePie",
        "https://www.viewstats.com/@veritasium/videos",
    ],
    "startUrls": [
        "https://www.viewstats.com/@MrBeast",
        "https://www.viewstats.com/@mkbhd/channelytics",
        "https://viewstats.com/@PewDiePie",
        "https://www.viewstats.com/@veritasium/videos",
    ],
    "searchTerms": ["mrbeast"],
    "proxy": { "useApifyProxy": True },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/viewstats-channel-analytics").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 '{
  "mode": "channels",
  "channels": [
    "https://www.viewstats.com/@MrBeast",
    "https://www.viewstats.com/@mkbhd/channelytics",
    "https://viewstats.com/@PewDiePie",
    "https://www.viewstats.com/@veritasium/videos"
  ],
  "startUrls": [
    "https://www.viewstats.com/@MrBeast",
    "https://www.viewstats.com/@mkbhd/channelytics",
    "https://viewstats.com/@PewDiePie",
    "https://www.viewstats.com/@veritasium/videos"
  ],
  "searchTerms": [
    "mrbeast"
  ],
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/viewstats-channel-analytics --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/viewstats-channel-analytics"
        }
    }
}
```

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/IVtKgCR7llEDAQW8G/builds/1QUSqZjrEFIu9GIHe/openapi.json
