# YouTube Outlier Finder: Breakout Videos vs Channel Median (`automatic/youtube-outlier-finder`) Actor

Find YouTube videos that beat their channel's median views. Score channels or a search term, filter by days back and minimum score, and get views per day, publish date, duration and rank. A Shorts mode scores each channel's Shorts. Empty input runs an example of up to 10 rows.

- **URL**: https://apify.com/automatic/youtube-outlier-finder.md
- **Developed by:** [Philip Kirkbride](https://apify.com/automatic) (community)
- **Categories:** Videos, Social media
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 ranked videos

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 Outlier Finder: Videos That Beat Their Channel

Find the YouTube videos that are doing far better than their channel usually does. Give it channels, a search term or both. A separate Shorts mode does the same for each channel's Shorts. Every recent upload gets an **outlier score**: its views divided by the median views of the channel's latest uploads. A score of 3 means three times the channel's usual views. You also get views per day, the publish date, duration and a rank, so the breakout videos come first.

Built for creators planning the next upload and for agencies reporting on a roster of channels. Every video is judged against its own channel's normal, so a 40,000-view video on a channel that usually gets 10,000 can outrank a 500,000-view video on a channel that always gets 400,000.

### Quick Start

The best recent uploads of two channels:

```json
{
  "channels": ["https://www.youtube.com/@mkbhd", "https://www.youtube.com/@LinusTechTips"],
  "lookbackDays": 30,
  "maxResults": 10
}
```

Videos from the last two weeks about home espresso that got at least twice their channel's usual views:

```json
{
  "searchQuery": "home espresso",
  "lookbackDays": 14,
  "minOutlierScore": 2,
  "maxResults": 20
}
```

The Shorts on two channels that beat the channel's recent Shorts at least tenfold:

```json
{
  "contentType": "shorts",
  "channels": ["https://www.youtube.com/@mkbhd", "https://www.youtube.com/@LinusTechTips"],
  "lookbackDays": 30
}
```

### First run: no input needed

Click **Start** without filling anything in. The Actor scores the latest uploads from two busy example channels, `@mkbhd` and `@LinusTechTips`, and returns up to 10 ranked rows. The `OUTPUT` record marks the run with `usedExample: true`, and the run status explains how to replace the sample with your own channels or search term. The rows are real, not a mock sheet.

The same happens if you send an empty JSON object (`{}`). Add `channels`, a `searchQuery`, or both to score your own list instead.

### Use cases

- **Creators: content ideas** — see which topics, titles and formats broke out in your niche, judged against each channel's own audience rather than raw views.
- **Agencies: channel portfolios** — run a client roster through one input and report each channel's over-performing uploads for the month.
- **Competitor tracking** — schedule a weekly run on competitor channels and collect only the uploads that beat their usual views.
- **Trend spotting** — search a topic and find the smaller channels whose recent video took off.
- **Thumbnail and title research** — export the top outliers with their thumbnails, publish dates and views per day for review.
- **Sponsorship and creator research** — spot channels whose reach is accelerating relative to their own track record.

### Recipes for creators and agencies

**A creator's weekly idea scan.** Search one niche, look at the last week and keep only videos at least twice as big as the channel's usual upload:

```json
{
  "searchQuery": "home espresso",
  "lookbackDays": 7,
  "minOutlierScore": 2,
  "maxResults": 50
}
```

Schedule it weekly. The same topic search each week shows which formats keep breaking out.

**An agency's monthly roster report.** Give every client or competitor channel once, widen the window and keep a generous number of rows:

```json
{
  "channels": [
    "https://www.youtube.com/@client-one",
    "https://www.youtube.com/@client-two",
    "https://www.youtube.com/@competitor-three"
  ],
  "lookbackDays": 30,
  "minOutlierScore": 1.5,
  "maxResults": 200
}
```

Group the rows by `channelId` for a per-channel section in a report. Each row already carries the channel name, handle, median views, views per day and publish date.

### Input

| Input | Type | Default | Meaning |
| --- | --- | --- | --- |
| `channels` | array | — | Up to 500 channel links, @handles or `UC…` channel IDs. Every upload in the window is scored. Paste one per line in Console or send a JSON array. |
| `searchQuery` | string | — | A search term. Recent videos for it are scored, each against its own channel. |
| `lookbackDays` | integer | `30` | Only uploads from the last this many days are scored (1-365). |
| `minOutlierScore` | number | `0` | Keep only videos with at least this score. `0` keeps every recent upload, ranked; `2` keeps videos with double the usual views. |
| `maxResults` | integer | `20` | How many top-ranked videos the whole run returns (1-1,000). A run with no channels and no search term returns at most 10. |
| `baselineVideos` | integer | `30` | How many of each channel's latest long-form uploads make up its median views (5-90). |
| `searchSort` | string | `relevance` | Which search results are scored: `relevance`, `uploadDate` or `viewCount`. |
| `contentType` | string | `long` | `long` scores long-form uploads. `shorts` scores each channel's Shorts (see [Shorts mode](#shorts-mode)). |
| `proxyConfiguration` | object | Apify proxy, US | Optional proxy settings; the default works for most runs. Use a direct connection, Apify datacenter proxy or your own proxy URLs. |

Use `channels`, `searchQuery` or both. A search scores up to three times `maxResults` recent results. With neither, the run does the 10-row example described above.

#### Bulk channels

`channels` accepts up to 500 entries in one run. Entries can be full links, @handles, `UC…` IDs or the short `youtube.com/@name` form, and duplicate channels are read once. In Console, paste a column of channels straight into the list, one per line; through the input JSON, send an array. Every upload published inside `lookbackDays` is scored, so `maxResults` only controls how many of the best rows come back.

Very large rosters take longer because every channel's recent uploads are read. For more than about 100 channels, split the list across runs or raise the run timeout. Each run's `OUTPUT` record names any channel that could not be scored and why.

### How the outlier score works

For each channel, the Actor builds a baseline from the channel's latest `baselineVideos` long-form uploads (30 by default), then compares every recent upload with that baseline:

1. Take the **median** views of the baseline uploads. The median is the middle value, so one past viral hit does not raise the bar for everything else the way an average would.
2. Divide each recent upload's views by that median: `outlierScore = viewCount ÷ channelMedianViews`.
3. Divide views by age in days to get `viewsPerDay`, so a 2-day-old video and a 20-day-old video can be compared fairly.

A channel needs at least 3 public uploads with views to get a baseline. Shorts, live streams and upcoming premieres are left out of both the scores and the baseline in long-form mode; Shorts mode compares Shorts with Shorts only.

**Worked example.** A channel's median is 12,000 views. One upload has 48,000 views after 6 days:

| Measure | Calculation | Result |
| --- | --- | --- |
| `outlierScore` | 48,000 ÷ 12,000 | **4.0** (four times the channel's usual views) |
| `viewsPerDay` | 48,000 ÷ 6 | **8,000** views per day |

**Reading the score.** The score is relative to the channel, not to YouTube as a whole:

| Score | What it means |
| --- | --- |
| around 1 | A normal upload for this channel. |
| 2 or more | Clearly above the channel's usual performance. |
| 3 to 5 | A strong breakout worth studying. |
| 5 to 10 | A big breakout, often reaching viewers beyond the channel's usual audience. |
| over 10 | Exceptional for the channel. Check `channelMedianViews`: very small channels can produce very high ratios from a handful of views. |

A high score does not explain why a video worked, and it says nothing about watch time or revenue. Use the title, thumbnail, duration, publish date and views per day together when you study the winners.

### What it returns

One row per video, best outlier first. All dates are ISO 8601 in UTC.

| Field | Meaning |
| --- | --- |
| `rank` | Position in this run; 1 is the highest outlier score. |
| `videoId`, `url`, `title`, `thumbnailUrl` | The video and its watch link. |
| `channelId`, `channelName`, `channelHandle`, `channelUrl` | The uploader. |
| `viewCount` | Views at the time of the run. |
| `channelMedianViews` | The channel's typical views: the median over its latest `baselineVideos` long-form uploads (30 by default). With an even number of uploads it can end in .5. |
| `outlierScore` | `viewCount` ÷ `channelMedianViews`. Only a median of exactly 0 is replaced, by 1. |
| `viewsPerDay` | `viewCount` ÷ `ageDays`. Videos younger than a day count as one day old. |
| `publishedAt`, `publishedDate`, `publishedDatePrecision` | Publish time and date. `exact`: the time is known to the second. `day`: the day is known, so `publishedAt` is null. `estimated`: the date is worked out from the upload age YouTube shows, for example "2 weeks ago". |
| `publishedText`, `ageDays` | The upload age as YouTube shows it, and days since publishing. |
| `duration`, `durationText` | Length in seconds and as `m:ss` / `h:mm:ss`. |
| `baselineVideos` | How many uploads the channel median was taken over. |
| `source`, `input`, `searchQuery`, `scrapedAt` | Which input found the video (`channel` or `search`), the input itself, the search term on search rows, and when the row was collected. |

**How exact the dates are.** Dates carry an `exact`, `day` or `estimated` precision label. Older uploads may have only day precision; unavailable dates or very recent uploads may remain estimated (counted in `OUTPUT.unconfirmedDates`). Videos with a known date outside `lookbackDays` are excluded. Estimated dates can make window coverage uncertain.

The `OUTPUT` record of the run's key-value store has the run summary: each channel with its median views, how many recent uploads were scored, and an `errors` list naming each input that could not be scored, with a `code` and a readable `message`.

### Shorts mode

Set `contentType` to `shorts` to score each channel's Shorts against that channel's own recent Shorts. Shorts mode reads channels only: a run with `contentType` `shorts` and a `searchQuery` stops at the start with `SEARCH_NOT_SUPPORTED_FOR_SHORTS`. Shorts views count plays, replays included, so they are never compared with long-form views.

**How a Short is scored.** The Shorts tab shows rounded-down view labels ("1.4K views" can be anything from 1,400 to 1,499). The baseline is the channel's newest `baselineVideos` Shorts that are at least `baselineMinAgeDays` old, and its median is taken over each label's upper bound, so it errs high. Each promising Short's exact views and publish day come from its own page, and the Short itself is left out of its baseline. A Short is returned only when its `scoreLowerBound` (exact views ÷ that cautious median) reaches `max(minOutlierScore, 10)`. It must also be at least 0 and at most `lookbackDays − 2` days old, belong to the channel, and have at least 10 other Shorts in its baseline. The publish days on Shorts pages may be one day off either way, so Shorts on the last two days of the window are reported as uncertain rather than returned.

**Shorts settings** (ignored in long-form runs):

| Input | Default | Range | Meaning |
| --- | --- | --- | --- |
| `lookbackDays` | `30` | 3-365 | Only Shorts published within this many days are returned. |
| `baselineVideos` | `30` | 11-90 | How many Shorts make up each channel's baseline. |
| `baselineMinAgeDays` | `3` | 0-14 | Younger Shorts stay out of the baseline; they can still be returned. |
| `baselineMaxAgeDays` | `365` | 30-730 | When the baseline's oldest Short is older than this, the channel's Shorts are reported as uncertain. |
| `maxTabPagesPerChannel` | `2` | 1-2 | Shorts tab pages read per channel (48 Shorts a page). |
| `maxDateLookupsPerChannel` | `8` | 2-16 | Shorts opened per channel to date the baseline. |
| `maxConfirmationsPerChannel` | `10` | 1-30 | Promising Shorts opened per channel to check exact views, day and owner. |
| `maxRequestsPerChannel`, `maxRequestsPerRun` | `30`, `400` | 5-80, 10-5,000 | Hard limits on requests to YouTube. |
| `maxBytesPerChannel`, `maxBytesPerRun` | `15`, `200` | 2-50, 10-2,000 | Download limits in MB; a run may slightly exceed them. |

**Shorts rows** have `contentType` `shorts`, `isShort` `true`, `contractVersion` `shorts-mode/1` and `contractRevision` `3`, and the same channel, title, view, date and score fields as long-form rows (`publishedAt`, `duration` and `durationText` are always null). Shorts rows add `url` as a `/shorts/` link, `tabPosition` (0 = the newest Short), `viewLabel`, `viewCountPrecision`, `shortsMedianViews` (same as `channelMedianViews`), `medianViewsUpperBound`, `scoreLowerBound`, `leaveOneOut`, `baselineNewestAgeDays` and `baselineOldestAgeDays`. Rows are ranked by `scoreLowerBound`, then `viewsPerDay`. `outlierScore` (exact views ÷ the median of the labels) is shown for reference only.

**Uncertain Shorts.** Shorts that looked like outliers but failed a check are not returned and not charged. Up to 200 of them are saved in the key-value store record `UNCERTAIN`, highest `outlierScore` first, each with `uncertainReasons`: `SCORE_BOUND_BELOW_THRESHOLD`, `VIEWS_NOT_EXACT`, `DATE_MISSING` (also when the page shows only an age such as "5 hours ago"), `DATE_IN_FUTURE`, `DATE_AT_WINDOW_EDGE`, `OWNER_MISMATCH`, `BASELINE_TOO_SMALL_AFTER_LOO`, `BASELINE_OLDER_THAN_CAP` or `ORDER_INCONSISTENT`.

**Shorts summary.** `OUTPUT` adds `acceptanceThreshold`, `anchorAt` and `anchorDate` (ages count from the run's start date, UTC), `caps`, `capsHit`, `refusedReservations`, row counts (`accepted`, `cutByMaxResults`, `cutByChargeLimit`, `uncertainTotal`, `uncertainListed`, `droppedOutsideWindow`, `droppedUnavailable`), `limitations` and one `channels` entry per channel. A channel entry holds its `status` (`ok`, `partial` or `error`) with `errorCode` and `errorDetail`, its counts, its baseline, the positions it dated, `windowCovered`, `requests` and `receivedBytes`. Its `unconfirmedProspects` lists promising Shorts that were not checked, as `{videoId, reason}` with reason `CAP_CONFIRMATIONS`, `BUDGET_EXHAUSTED`, `OUT_OF_TIME` or `FETCH_FAILED`. A run that hits a limit or the timeout ends `partial` and still succeeds, even with no rows.

Shorts channel codes: `TOO_FEW_SHORTS` (no Shorts tab, or fewer than 10 Shorts old enough for a baseline), `DATE_BOUNDARY_UNRESOLVED` (the channel's newest Short, or the Shorts around the minimum baseline age, could not be dated; `errorDetail` `budget`, `unavailable` or `noDate`), `ORDER_INCONSISTENT` (dated Shorts were not newest first; the channel's rows are withheld), `BUDGET_EXHAUSTED` (a limit stopped the channel before its baseline was complete, or before it started) and `PARTIAL` (a limit stopped the checks of promising Shorts; checked rows stand).

**Shorts limitations**, as also given in `OUTPUT.limitations`:

> shorts-mode/1 limitations. (1) Channels only; search is not supported. (2) Shorts tab view labels are truncated floors; baseline medians use label upper bounds and are conservative; `outlierScore` is informational and can overstate; accepted rows are gated on `scoreLowerBound`. (3) Tab order is assumed newest first. It is checked at each channel's dated positions, and an inversion withholds that channel's rows. Shorts at undated positions are placed in the baseline by this assumption, which is not proof of their age. (4) Publish days have timezone uncertainty. A shown day may be one day off in either direction, so both window edge days are reported as uncertain. The same ±1 day can move the min-age boundary by one position or move the baseline across the age cap; those effects are not flagged per row. (5) Only Shorts on the tab pages read are considered; a channel posting more Shorts within the window may be partly covered (`windowCovered`). (6) Shorts views count plays including replays and are not comparable with long-form views. (7) Scores describe view counts relative to the channel's recent Shorts at read time; they do not explain why a Short performed and say nothing about retention or watch time. (8) No number of results per run is implied. (9) Request caps are hard; byte caps are pre-check budgets that a run can exceed by up to the responses in flight; actual requests and bytes are reported.

### Output example

Illustrative rows (live values change constantly):

```jsonl
{"rank": 1, "videoId": "FH5SoaRx2TA", "url": "https://www.youtube.com/watch?v=FH5SoaRx2TA", "title": "There’s a Secret (Better) Netflix for Millionaires", "channelId": "UCXuqSBlHAE6Xw-yeJA0Tunw", "channelName": "Linus Tech Tips", "channelHandle": "@LinusTechTips", "channelUrl": "https://www.youtube.com/@LinusTechTips", "publishedAt": "2026-10-22T17:00:06Z", "publishedDate": "2026-10-22", "publishedDatePrecision": "exact", "publishedText": "4 days ago", "ageDays": 4.91, "viewCount": 4658690, "channelMedianViews": 1900000, "outlierScore": 2.45, "viewsPerDay": 948269.2, "duration": 1079, "durationText": "17:59", "baselineVideos": 30, "thumbnailUrl": "https://i.ytimg.com/vi/FH5SoaRx2TA/hqdefault.jpg", "source": "channel", "input": "https://www.youtube.com/@LinusTechTips", "searchQuery": null, "scrapedAt": "2026-10-27T14:54:43Z"}
{"rank": 3, "videoId": "t28bEMboL9Y", "url": "https://www.youtube.com/watch?v=t28bEMboL9Y", "title": "The All COSTCO Gaming Setup", "channelId": "UCXuqSBlHAE6Xw-yeJA0Tunw", "channelName": "Linus Tech Tips", "channelHandle": "@LinusTechTips", "channelUrl": "https://www.youtube.com/@LinusTechTips", "publishedAt": null, "publishedDate": "2026-10-06", "publishedDatePrecision": "day", "publishedText": "2 weeks ago", "ageDays": 21.62, "viewCount": 2648717, "channelMedianViews": 1900000, "outlierScore": 1.39, "viewsPerDay": 122497.4, "duration": 1538, "durationText": "25:38", "baselineVideos": 30, "thumbnailUrl": "https://i.ytimg.com/vi/t28bEMboL9Y/hqdefault.jpg", "source": "channel", "input": "https://www.youtube.com/@LinusTechTips", "searchQuery": null, "scrapedAt": "2026-10-27T14:54:43Z"}
{"rank": 1, "videoId": "GGvrd35fXbI", "url": "https://www.youtube.com/watch?v=GGvrd35fXbI", "title": "The Ultimate Home Espresso Machines Under £2,000 ☕ 4 Machines Compared!", "channelId": "UCWaZ48TYzu_DX-s8ceMmHJA", "channelName": "Bella Barista", "channelHandle": "@bellabaristauk1", "channelUrl": "https://www.youtube.com/@bellabaristauk1", "publishedAt": "2026-10-25T23:17:25Z", "publishedDate": "2026-10-25", "publishedDatePrecision": "exact", "publishedText": "1 day ago", "ageDays": 1.65, "viewCount": 11783, "channelMedianViews": 3050, "outlierScore": 3.86, "viewsPerDay": 7131.7, "duration": 2715, "durationText": "45:15", "baselineVideos": 30, "thumbnailUrl": "https://i.ytimg.com/vi/GGvrd35fXbI/hqdefault.jpg", "source": "search", "input": "home espresso", "searchQuery": "home espresso", "scrapedAt": "2026-10-27T14:59:38Z"}
{"contractVersion": "shorts-mode/1", "contractRevision": 3, "contentType": "shorts", "isShort": true, "shortsIdentity": "shortsTab", "rank": 1, "videoId": "XXXXXXXXXXX", "title": "An illustrative Short", "thumbnailUrl": "https://i.ytimg.com/vi/XXXXXXXXXXX/hqdefault.jpg", "url": "https://www.youtube.com/shorts/XXXXXXXXXXX", "tabPosition": 1, "channelId": "UCXuqSBlHAE6Xw-yeJA0Tunw", "channelName": "Linus Tech Tips", "channelHandle": "@LinusTechTips", "channelUrl": "https://www.youtube.com/@LinusTechTips", "viewCount": 4215873, "viewCountPrecision": "exact", "viewLabel": "4.2M views", "publishedDate": "2026-11-01", "publishedDatePrecision": "day", "publishedAt": null, "ageDays": 3, "channelMedianViews": 210000, "shortsMedianViews": 210000, "medianViewsUpperBound": 220000, "outlierScore": 20.08, "scoreLowerBound": 19.16, "baselineVideos": 29, "leaveOneOut": false, "baselineNewestAgeDays": 3, "baselineOldestAgeDays": 61, "viewsPerDay": 1405291.0, "duration": null, "durationText": null, "scrapedAt": "2026-11-04T12:00:00Z"}
```

### Limits and caveats

- In the default long-form mode, scores cover long-form uploads from each channel's Videos tab. Shorts, live streams and upcoming premieres are left out of both the scores and the medians, in channel and search mode alike. Shorts mode scores Shorts only (see [Shorts mode](#shorts-mode)).
- The median is taken over each channel's latest 30 long-form uploads by default (`baselineVideos`). A channel whose latest uploads were unusual (a viral run, a long break) gets a skewed baseline.
- View counts of a channel's older uploads are rounded the way YouTube shows them (for example `1.9M`), so `channelMedianViews` can be a rounded figure. The views of the most recent uploads and of every returned row are exact.
- Small channels give noisy scores: a video with 134 views on a channel with a median of 8 scores 16.75. Filter on `channelMedianViews` if you only want established channels.
- Older videos have had more time to collect views. Compare `viewsPerDay` when ages differ a lot, or use a shorter `lookbackDays`.
- A channel needs at least 3 public uploads with views to get a median; others are listed in `OUTPUT.errors` as `TOO_FEW_VIDEOS`.
- A run that scores channels but finds nothing at or above `minOutlierScore` succeeds with an empty dataset. A run where no channel could be scored ends with an error.
- Close to the run's timeout, the run stops reading new pages, ranks what it has and saves it. The run still succeeds, with the status "Stopped before the run timeout; saved N videos". `OUTPUT` then has `timedOut: true` and lists the inputs it did not finish as `OUT_OF_TIME`. For big channel lists, raise the run timeout.
- Use this actor only where you have the necessary permissions and a lawful basis; YouTube's terms restrict automated access.

Error codes you may see in `OUTPUT.errors`: `INVALID_INPUT`, `CHANNEL_NOT_FOUND`, `CHANNEL_UNAVAILABLE`, `TOO_FEW_VIDEOS`, `NO_RESULTS`, `PARTIAL`, `OUT_OF_TIME`, `EMPTY_RESPONSE`, `RATE_LIMITED`, `BLOCKED`, `UPSTREAM_ERROR`, `NETWORK_ERROR` and `SOURCE_CHANGED`. Shorts mode adds `TOO_FEW_SHORTS`, `DATE_BOUNDARY_UNRESOLVED`, `ORDER_INCONSISTENT` and `BUDGET_EXHAUSTED`.

### FAQ

**How is the outlier score worked out?** For each channel, the run takes the median views of its latest `baselineVideos` uploads. Each recent upload's views are divided by that median. The median is used rather than the average, so one viral video does not raise the bar for the rest. There is a longer explanation with a worked example under [How the outlier score works](#how-the-outlier-score-works).

**What happens if I leave the input empty?** The run scores the latest uploads of `@mkbhd` and `@LinusTechTips`, returns up to 10 rows, and marks `OUTPUT.usedExample` as `true`. Add your own channels or a search term to score those instead.

**Can I check many channels at once?** Yes, up to 500 channels per run. Duplicate channels are read once, and every upload in the window is scored before the best rows are returned. Split very large lists across runs.

**Can I get outliers per channel instead of overall?** The ranking covers the whole run. Run one channel at a time, or group the rows by `channelId`.

**Why are there no rows?** The run found recent uploads but none reached `minOutlierScore`, or the channels had too few uploads to build a baseline. Lower the score or widen `lookbackDays`. Channels that could not be scored are named in `OUTPUT.errors`.

**How do I keep the cost predictable?** Set `maxResults`; the run returns at most that many rows, and only delivered rows are charged.

### Price

**$0.001 per delivered ranked video row** ($1 per 1,000 rows), plus **$0.005 per run start**. The run-start event is charged per GB of memory; the default 512 MB run is one start event. The start is charged even if the run saves no results. Only the ranked rows you receive are charged: videos that are scored but not returned, and channels or searches that fail, cost nothing. Shorts mode uses the same prices: only returned Shorts rows are charged, and the uncertain Shorts and the summary are free. A Shorts run reads more pages per channel than a long-form run, and it may return no rows at all. Platform usage is included in the price.

| Delivered rows | Event cost |
| ---: | ---: |
| 10 (the no-input example) | $0.015 |
| 100 | $0.105 |
| 1,000 | $1.005 |

A run that returns fewer rows costs less; a run that returns nothing still costs the $0.005 start.

### Support

For questions or issues, contact us from the Actor page.

# Actor input Schema

## `contentType` (type: `string`):

long (default) scores long-form uploads from channels and search. shorts scores each channel's Shorts against its own recent Shorts; it reads channels only, so a search term with shorts stops the run.

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

Channel links, @handles or channel IDs, for example https://www.youtube.com/@mkbhd, @mkbhd or UCBJycsmduvYEL83R_U4JriQ. Paste up to 500 channels one per line or send an array; duplicate channels are read once. Every upload in the lookback window is scored. Shorts mode reads channels only.

## `searchQuery` (type: `string`):

Find recent videos on a topic, then score each one against its own channel. Combine with Channels or use on its own. Leave Channels and Search term empty to run the 10-row example.

## `lookbackDays` (type: `integer`):

Only uploads published within this many days are scored. Shorts mode needs at least 3.

## `minOutlierScore` (type: `number`):

Keep only videos with at least this many times their channel's median views. 0 keeps every recent upload, ranked; 2 keeps videos with double the usual views.

## `maxResults` (type: `integer`):

How many top-ranked videos to return for the whole run. With no channels and no search term, the example run returns at most 10.

## `baselineVideos` (type: `integer`):

How many of each channel's latest long-form uploads make up its median views. In Shorts mode: how many of its Shorts old enough for the baseline (at least 11).

## `searchSort` (type: `string`):

Which videos the search term brings back before scoring: YouTube's relevance ranking, the newest uploads or the most viewed.

## `baselineMinAgeDays` (type: `integer`):

Shorts mode only. Shorts younger than this stay out of the channel baseline (they can still be returned).

## `baselineMaxAgeDays` (type: `integer`):

Shorts mode only. When the oldest Short in the baseline is older than this, the channel's Shorts are reported as uncertain instead of returned.

## `maxTabPagesPerChannel` (type: `integer`):

Shorts mode only. How many pages of each channel's Shorts tab may be read (48 Shorts a page).

## `maxDateLookupsPerChannel` (type: `integer`):

Shorts mode only. How many Shorts per channel may be opened to find their publish day for the baseline.

## `maxConfirmationsPerChannel` (type: `integer`):

Shorts mode only. How many promising Shorts per channel are opened to check their exact views, day and owner.

## `maxRequestsPerChannel` (type: `integer`):

Shorts mode only. Hard limit on requests to YouTube for one channel.

## `maxBytesPerChannel` (type: `integer`):

Shorts mode only. Download budget per channel in MB (1 MB = 1,000,000 bytes). Checked before each request, so a channel can pass it by the pages already on their way.

## `maxRequestsPerRun` (type: `integer`):

Shorts mode only. Hard limit on requests to YouTube for the whole run.

## `maxBytesPerRun` (type: `integer`):

Shorts mode only. Download budget for the whole run in MB (1 MB = 1,000,000 bytes). Checked before each request, so a run can pass it by the pages already on their way.

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

The default works for most runs. Change it only if you need a specific country or your own proxies. Use a direct connection, Apify datacenter proxy or your own proxy URLs; other Apify proxy groups are refused.

## Actor input object example

```json
{
  "contentType": "long",
  "channels": [
    "https://www.youtube.com/@mkbhd",
    "https://www.youtube.com/@LinusTechTips"
  ],
  "lookbackDays": 30,
  "minOutlierScore": 0,
  "maxResults": 10,
  "baselineVideos": 30,
  "searchSort": "relevance",
  "baselineMinAgeDays": 3,
  "baselineMaxAgeDays": 365,
  "maxTabPagesPerChannel": 2,
  "maxDateLookupsPerChannel": 8,
  "maxConfirmationsPerChannel": 10,
  "maxRequestsPerChannel": 30,
  "maxBytesPerChannel": 15,
  "maxRequestsPerRun": 400,
  "maxBytesPerRun": 200,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `results` (type: `string`):

One row per returned video, best first. Shorts mode rows carry contractVersion shorts-mode/1 and are gated on scoreLowerBound.

## `summary` (type: `string`):

Run summary. Shorts mode adds the acceptance threshold, caps and capsHit, row counts (accepted, cutByMaxResults, cutByChargeLimit, uncertainTotal, uncertainListed, droppedOutsideWindow, droppedUnavailable), limitations and channels\[]; each channel lists unconfirmedProspects as {videoId, reason} objects, reason one of CAP_CONFIRMATIONS, BUDGET_EXHAUSTED, OUT_OF_TIME or FETCH_FAILED. A bare-start example run adds usedExample: true.

## `uncertain` (type: `string`):

Up to 200 Shorts that looked like outliers but failed a check, highest outlierScore first, each with uncertainReasons. Not in the dataset and not charged.

# 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": [
        "https://www.youtube.com/@mkbhd",
        "https://www.youtube.com/@LinusTechTips"
    ],
    "maxResults": 10,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("automatic/youtube-outlier-finder").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": [
        "https://www.youtube.com/@mkbhd",
        "https://www.youtube.com/@LinusTechTips",
    ],
    "maxResults": 10,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("automatic/youtube-outlier-finder").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": [
    "https://www.youtube.com/@mkbhd",
    "https://www.youtube.com/@LinusTechTips"
  ],
  "maxResults": 10,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyCountry": "US"
  }
}' |
apify call automatic/youtube-outlier-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automatic/youtube-outlier-finder"
        }
    }
}
```

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/2NKiIKOv4zFpIJuTJ/builds/Kgk6EzkbSRfvI3odk/openapi.json
