# YouTube Outlier Finder: Videos That Beat Their Channel (`barefoot_grade/youtube-outlier-finder`) Actor

Find YouTube videos that beat their channel's typical views. Give channels or a search term; each recent upload gets an outlier score (views ÷ the channel's median views), views per day, publish date, duration and a rank. Filter by days back and minimum score.

- **URL**: https://apify.com/barefoot_grade/youtube-outlier-finder.md
- **Developed by:** [Philip Kirkbride](https://apify.com/barefoot_grade) (community)
- **Categories:** Videos, Social media
- **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. 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.

### 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
}
```

### Use cases

- **Content ideas**: see which topics and titles broke out for creators in your niche, judged against each channel's own audience rather than raw views.
- **Competitor tracking**: schedule a weekly run on a list of competitor channels and get their over-performing uploads.
- **Trend spotting**: search a topic and find the small channels whose recent video took off.
- **Thumbnail and title research**: export the top outliers with their thumbnails for review.

### Input

| Input | Type | Default | Meaning |
| --- | --- | --- | --- |
| `channels` | array | — | Channel links, @handles or `UC…` channel IDs. Every upload in the window is scored. |
| `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. |
| `maxResults` | integer | `20` | How many top-ranked videos the whole run returns (1-1,000). |
| `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`. |
| `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. With neither, the run checks two example channels (`@mkbhd` and `@LinusTechTips`). A search scores up to three times `maxResults` recent results.

### 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.** Both modes date videos the same way. Each channel's newest 15 uploads get an `exact` publish time. An older upload starts from the age YouTube shows ("2 weeks ago"). Before such a row is returned, the run opens the video's page for its publish day and current views (`day`). A row stays `estimated` only when that page could not be read (counted in `OUTPUT.unconfirmedDates`) or showed an age instead of a date, as it does for a video a few hours old. A row whose real date falls outside `lookbackDays` is dropped, and the next best video takes its place.

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`.

### 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"}
```

### Limits and caveats

- 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.
- 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`.

### 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.

**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`.

**How do I keep the cost predictable?** Set `maxResults`; the run returns at most that many rows.

### 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. Platform usage is included in the price.

### Support

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

# Actor input Schema

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

Channel links, @handles or channel IDs, for example https://www.youtube.com/@mkbhd, @mkbhd or UCBJycsmduvYEL83R_U4JriQ. Every upload in the lookback window is scored.

## `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.

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

Only uploads published within this many days are scored.

## `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.

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

How many of each channel's latest long-form uploads make up its median views.

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

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

## `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
{
  "channels": [
    "https://www.youtube.com/@mkbhd",
    "https://www.youtube.com/@LinusTechTips"
  ],
  "lookbackDays": 30,
  "minOutlierScore": 0,
  "maxResults": 10,
  "baselineVideos": 30,
  "searchSort": "relevance",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

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

No description

## `summary` (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": [
        "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("barefoot_grade/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("barefoot_grade/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 barefoot_grade/youtube-outlier-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,barefoot_grade/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/UGZiS2efrI9kNpJ86/openapi.json
