# YouTube Channel Email Scraper - Business Contacts (`neverempty/youtube-channel-emails`) Actor

Business email for a list of YouTube channels, from what each channel publishes itself. When a channel gives no email but links a website, that site is checked too. Every address says where it came from, and a channel with nothing published returns the reason - and is not charged.

- **URL**: https://apify.com/neverempty/youtube-channel-emails.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Lead generation, Social media, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $17.02 / 1,000 contact founds

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?

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

## YouTube Channel Email Scraper - Business Contacts

Give it a list of YouTube channels. It returns the **business email each channel publishes itself**, and
says **where each address came from**. When a channel publishes no email but links a website, the site and
its contact page are checked too.

Nothing here comes from behind YouTube's "view email address" button - that asks a human to pass a check,
and this Actor does not touch it. Only what a channel puts in public.

### What you actually get, measured

30 channels were checked on **2026-09-02**. The channel list, the per-channel result and the script that
produced them ship with this Actor (`test/survey-channels.mjs`, `test/_channel-survey.json`), so the number
below can be reproduced or challenged. All 30 pages were readable.

| | Channels | Share |
|---|---|---|
| Email published in the channel description | **6** | 20% |
| No email, but a website is linked | **7** | 23% |
| Neither | 17 | 57% |

So **fewer than half of channels are reachable** - directly, or through the site they link. **The rest
publish nothing**, and no scraper can change that. This one tells you which group a channel is in instead of
returning a blank row that could mean anything.

### One row per channel

| Field | Example |
|---|---|
| `channelId` | `UCBJycsmduvYEL83R_U4JriQ` |
| `title` | `Marques Brownlee` |
| `emails` | `["business@mkbhd.com"]` |
| `emailSources` | `["channel description"]` or `["https://example.com/contact"]` |
| `websites` | `["https://mkbhd.com"]` |
| `contactPagesCrawled` | how many pages of that site were opened |
| `ok` | `true` only when at least one address was found |
| `reason` | why the row is empty, when it is |

`emailSources` is the part most tools leave out. An address found in the channel's own description and an
address found on some page of a linked site are **not** the same quality of lead, and you should be able
to tell them apart before you write to anyone.

### The channel you asked for, not the one next to it

A YouTube channel page carries data for **other** channels too - recommendations, shelves, related creators.
Reading "the first channel id on the page" gives you the wrong channel, and the output looks perfectly
normal while being wrong. (We know because we did exactly that in a different project on 2026-08-29 and
published numbers for the wrong channels.)

This Actor reads **only** the page's own `channelMetadataRenderer` block, and a test feeds it a page with a
second channel embedded to prove the wrong one is never picked.

### Input

```json
{
  "channels": ["@mkbhd", "https://www.youtube.com/@LinusTechTips", "UCYO_jab_esuFRV4b17AJtAw"],
  "crawlWebsite": true,
  "maxPagesPerSite": 3,
  "respectRobots": true
}
```

Handles, channel URLs and `UC…` ids all work and can be mixed. Anything unreadable comes back as a row
saying so - never as a silent skip.

| Field | Default | Meaning |
|---|---|---|
| `channels` | *(required)* | What to look up |
| `crawlWebsite` | `true` | Also open the website a channel links, when it publishes no email |
| `maxPagesPerSite` | `3` | How many pages of that site to open before giving up. Stops at the first address |
| `respectRobots` | `true` | Skip what the site disallows |
| `timeoutSecs` | `20` | Per page |

### Pricing

**Charged only for channels where an address was actually found.** Channels that publish nothing, channels
that could not be read, and inputs that were not channels are all delivered with a reason **and cost nothing**.

That matters here more than in most Actors: on the list above, 57% of channels published no contact at all.
Some Actors in this category bill per input row and others, like this one, bill per contact found - worth
checking which one you are buying, because on a list like that the difference is more than double.

### Checks

**120 automated checks**, and **16 deliberate defects** in the shared extraction layer plus 13 more aimed at
the channel parser - 29 in total. Every defect is inserted on purpose to confirm a check turns red: picking
the wrong channel's data, accepting a broken metadata block, treating social links as the creator's website,
marking an empty row as `ok`, dropping the source of an address, letting a no-reply or placeholder address
count as a contact, stamping rows with the current time instead of the run time. All 29 are caught
(`npm test`, `npm run mutate`).

### Other tools by NeverEmpty

- **[youtube-transcript-reliable](https://apify.com/neverempty/youtube-transcript-reliable)** - transcripts, with the failure reason when there is none
- **[youtube-channel-transcripts](https://apify.com/neverempty/youtube-channel-transcripts)** - the same engine across a whole channel
- **[website-contacts](https://apify.com/neverempty/website-contacts)** - emails, phones and socials from a list of websites

# Actor input Schema

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

@handles, channel URLs, or UC… channel ids. Mix them freely. Anything else comes back as a row saying it was not readable, not as a silent skip. Leave it out entirely and three well-known channels are used, so a bare call still returns real rows.

## `crawlWebsite` (type: `boolean`):

When the channel publishes no email but does link a website in its description, open that site and its contact page to look for an address. Robots rules are respected.

## `maxPagesPerSite` (type: `integer`):

How many pages of the linked website to open before giving up. Stops as soon as an address is found.

## `respectRobots` (type: `boolean`):

Skip pages the site disallows. Leave on unless you own the site.

## `timeoutSecs` (type: `integer`):

How long to wait for one page of the linked website before giving up.

## Actor input object example

```json
{
  "channels": [
    "@mkbhd",
    "@LinusTechTips",
    "@3blue1brown"
  ],
  "crawlWebsite": true,
  "maxPagesPerSite": 3,
  "respectRobots": true,
  "timeoutSecs": 20
}
```

# Actor output Schema

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

One row per channel: the emails it publishes, where each one was found, the websites it links, and - when there is nothing - the reason.

# API

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

## JavaScript example

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

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

// Prepare Actor input
const input = {
    "channels": [
        "@mkbhd",
        "@LinusTechTips",
        "@3blue1brown"
    ]
};

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

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

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

```

## Python example

```python
from apify_client import ApifyClient

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

# Prepare the Actor input
run_input = { "channels": [
        "@mkbhd",
        "@LinusTechTips",
        "@3blue1brown",
    ] }

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

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

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

```

## CLI example

```bash
echo '{
  "channels": [
    "@mkbhd",
    "@LinusTechTips",
    "@3blue1brown"
  ]
}' |
apify call neverempty/youtube-channel-emails --silent --output-dataset

```

## MCP server setup

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

```

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/d6hXSzdaiBX4yLYig/builds/4WiUCjseQoLw9XDii/openapi.json
