Skip to content

ChocoData-com/youtube-email-scraper

Repository files navigation

YouTube Email Scraper

YouTube Email Scraper

YouTube Email Scraper for extracting a channel's public contact record: business email, website, social profiles and About links from YouTube.com channels. This repo has a free YouTube contact web scraping script you can run right now, and a YouTube channel contact API that returns the public contact block for one channel.

This is the channel-contact endpoint on its own. The YouTube Scraper repo covers the rest of the surface: video, transcript, search, playlists, comments and Shorts.

This repo reads public brand, media and institutional channels only. Every capture and screenshot on this page targets an organisation channel (@NASA, @TED, @Microsoft). It is not tooling for profiling a private individual.

Last updated: 2026-07-20. Working against YouTube.com as of July 2026, and re-verified whenever YouTube changes their markup.

Every JSON block on this page was captured from the live API on 2026-07-20. Long arrays are trimmed and each block says exactly what was cut; the fields shown are verbatim. Full uncut samples are committed in youtube_email_scraper_api_data/. Every code example calls the actual API and is runnable from youtube_email_scraper_api_codes/.

pip install requests
export CHOCODATA_API_KEY="your_key"     # free: 1,000 requests, one-time, no card
python youtube_email_scraper_api_codes/contact.py @NASA

Those lines return this, live from YouTube.com:

{
  "channel_name": "NASA",
  "channel_id": "UCLA_DiR1FfKNvjuUpBHmylQ",
  "email_count": 0,
  "links_count": 6,
  "links": [{ "title": "nasa.gov", "url": "https://www.nasa.gov/", "from": "links_section" }],
  "reveal_available": false
}

Point it at a list of handles and you get one contact row each:

Retrieved YouTube channel contact data

What this endpoint gives you, and what populates. It reliably returns the channel's public contact block: the channel id and name, and every link in the About section (the website, the social profiles, and any booking or media-request page). It also reads a business email when the creator typed one in plain text in the description or a link row. Across 43 public brand, media and creator channels sampled on 2026-07-20, 4 returned a public email and 39 did not, so this page leads with the links, which populate on almost every channel.


Contents


Free YouTube Contact Scraper

YouTube server-renders a channel's About page into a JavaScript object called ytInitialData, so you can read the public contact surface without a headless browser or a login. No key, no cost:

python free_scraper/youtube_email_free_scraper.py @NASA
python free_scraper/youtube_email_free_scraper.py https://www.youtube.com/@TED

Source: free_scraper/youtube_email_free_scraper.py. It brace-counts ytInitialData out of the HTML, reads channelMetadataRenderer for the id, name and description, walks every channelExternalLinkViewModel for the About links, and runs the same plain-text email parse the API uses.

After running the command, your terminal should look something like this:

Free YouTube contact scraper reading a channel About page

It works. @NASA returns the channel id, name and 6 About links from a plain requests.get, no login. What it does not get is the business email, and that is not a bug in the script, it is where YouTube put the data. The next section is about that.

Avoid getting blocked when scraping YouTube channels

The About page loads fine for a cold client. The hard parts are where the data lives and how the links are stored, and all of them are measured against YouTube on 2026-07-20.

First, the "business email" is not in the page. YouTube hides a channel's business email behind a "View email address" button that triggers a reCAPTCHA and requires a logged-in session, so the address is never in the About page HTML. No HTML scraper, free or paid, gets it. The reveal_available: false field says so explicitly. What this endpoint returns in the emails array is only what a creator additionally typed in plain text in the description or a link row, which is why it populates on a minority of channels.

Second, the About "Links" URLs are redirect-wrapped. Each link row stores its target as a YouTube /redirect?...&q=<url-encoded destination> wrapper, not the real URL. A scraper that reads the visible display text gets a bare domain with no scheme (nasa.gov), and one that reads the raw href gets YouTube's redirect URL. The real destination has to be pulled out of the q= parameter and URL-decoded, which is what the url field already contains.

Third, the handle has to resolve. A channel is addressable by @handle, channel id (UC...), legacy /c/ or /user/ path, or a full URL, and the API accepts all of them. But a wrong or renamed handle returns a 404, not an empty result: in the sample above, two well-known brands answered 404 on the obvious @handle because their real handle differs. Confirm the handle before a bulk run.

Here is the full picture, and what each item costs you:

What bites you Why What it costs you
The business email is reCAPTCHA-gated It sits behind a "View email address" button and a login, never in the HTML. No HTML scraper reaches it; emails only carries plain-text addresses a creator published.
Most brand channels publish no plain-text email 39 of 43 sampled returned an empty emails array. Email coverage is a minority; the About links are the reliable contact route.
Link URLs are /redirect wrappers The real target is url-encoded in a q= param. Read the display text and you get a scheme-less bare domain; the url field is already resolved.
A wrong handle returns 404 Handles get renamed and are case- and form-sensitive. A bulk run silently drops channels unless you handle the 404 and confirm handles.
The About page is per-IP rate limited Fetching many channels from one address gets throttled. Bulk contact work needs IP rotation you would otherwise build and maintain.

So the two paths, side by side, same channels, same day:

Free YouTube contact scraper vs Chocodata API, measured

Both read the same public About page and neither reaches the gated business email. The difference is that the API resolves the redirect-wrapped link URLs, adds the links a creator drops into the description text (on @TED, one extra), and handles the per-IP throttle for a bulk run.


Using the Chocodata YouTube Email Scraper API

The managed option, and the one this repo is built around: the Chocodata YouTube Email Scraper API. One GET request per channel returns the public contact record: id, name, the resolved About links tagged by where they came from, and any plain-text email, with a ~99% success rate and no proxy management. Free for the first 1,000 requests.


YouTube Email Scraper API reference

Below is the YouTube Email Scraper API reference to get you started: authentication, the error bodies, the rate limits, and the endpoint itself.

Quickstart

curl "https://api.chocodata.com/api/v1/youtube/email?api_key=YOUR_KEY&channel=@NASA"
import requests

r = requests.get(
    "https://api.chocodata.com/api/v1/youtube/email",
    params={"api_key": "YOUR_KEY", "channel": "@NASA"},
    timeout=90,
)
c = r.json()
print(c["channel_name"], c["links_count"], c["email_count"])
# NASA 6 0

After running the command, your terminal should look something like this:

Running the YouTube Email Scraper API

Authentication

Pass your key as the api_key query parameter. There is no header form and no OAuth step.

https://api.chocodata.com/api/v1/youtube/email?api_key=YOUR_KEY&channel=@NASA

A free key is 1,000 requests, one time, with no card. Get one at chocodata.com.

Errors

Nothing below is billed: you are only charged on a 2xx.

Status error code Meaning Billed What to do
400 invalid_params channel was not supplied. The body names the missing param and its path. no Add channel.
401 INVALID_API_KEY Key missing, unrecognised, or revoked. no Check api_key. Get one at chocodata.com.
402 INSUFFICIENT_CREDITS Balance exhausted. no Top up or upgrade.
404 item_not_found No channel exists for that handle or id. no Check the @handle or channel id.
429 RATE_LIMITED Over 120 requests/60s, or over your plan's concurrency. no Back off and retry.

A bad key, verbatim:

curl "https://api.chocodata.com/api/v1/youtube/email?api_key=totally_invalid_key&channel=@NASA"
{"error": {"code": "INVALID_API_KEY", "message": "Api key not recognised."}}

A missing required param, verbatim. Note that it names the real parameter, channel:

{"error": "invalid_params", "issues": [{"code": "invalid_type", "expected": "string", "received": "undefined", "path": ["channel"], "message": "Required"}]}

Two response shapes exist: auth and billing errors nest under error.code (uppercase), while parameter and scrape-layer errors are flat with a lowercase error string.

The scripts in this repo turn each of these errors into an actionable message, so a typo'd key does not hand you a stack trace:

YouTube Email Scraper API error handling

Rate limits and concurrency

Two separate limits apply, and they are enforced independently.

Limit Value
Requests per key 120 per 60 seconds (sliding window)
Concurrent requests, Free 10
Concurrent requests, Vibe 30
Concurrent requests, Pro 50
Concurrent requests, Custom 100

Exceed either and you get 429, not a queue. Every call is a synchronous GET: there is no webhook, callback, or async job to poll. The examples use timeout=90 for headroom.

Fan out with a thread pool, sized to stay inside both limits at once:

from concurrent.futures import ThreadPoolExecutor
import requests

def one(channel):
    r = requests.get("https://api.chocodata.com/api/v1/youtube/email",
                     params={"api_key": KEY, "channel": channel}, timeout=90)
    return r.json() if r.status_code == 200 else None

with ThreadPoolExecutor(max_workers=4) as pool:
    contacts = [c for c in pool.map(one, channels) if c]

Email: channel emails, contact links, social profiles and website

The public contact record for one channel: id, name, the About links tagged by source, any plain-text email, and the reveal_available flag.

Param Type Required Default Description
channel string yes - @handle, channel id (UC...), /c/ or /user/ path, or a full channel URL. A wrong or renamed handle returns 404.
country string no US ISO-3166 alpha-2 egress country hint.
api_key string yes - Your key. Query parameter, not a header.
curl "https://api.chocodata.com/api/v1/youtube/email?api_key=YOUR_KEY&channel=@NASA"

Real response. links cut to 2 of 6; links_count is the real total, and every other field is verbatim (full sample):

{
  "channel": "NASA",
  "channel_id": "UCLA_DiR1FfKNvjuUpBHmylQ",
  "channel_name": "NASA",
  "emails": [],
  "email_count": 0,
  "links": [
    { "title": "nasa.gov", "display": "nasa.gov", "url": "https://www.nasa.gov/", "from": "links_section" },
    { "title": "X", "display": "x.com/nasa", "url": "https://x.com/nasa", "from": "links_section" }
  ],
  "links_count": 6,
  "source": "public_description",
  "reveal_available": false
}

links is the field most people actually use, because it is the contact route on a brand channel: the website, the social profiles, and any booking or media page. A few things worth knowing, all measured on the channels sampled on 2026-07-20:

  • emails is populated only when a creator publishes an address in plain text. Across 43 public brand, media and creator channels sampled, 4 returned a public business email in this array and 39 returned []; the reCAPTCHA-gated "View email address" address is never included, which reveal_available: false signals.
  • url is the resolved destination, not YouTube's redirect wrapper. The API pulls the real target out of the /redirect?...&q= parameter and URL-decodes it, so url is ready to use while display keeps the scheme-less text YouTube shows.
  • from tags where each link came from: links_section is the structured About "Links" widget; description is a URL or social handle parsed out of the description blurb. The second sample shows both.
  • channel_id is the stable identifier. Handles get renamed; the UC... id does not, so store that if you are tracking a channel over time.

A second committed sample, email_ted.json, is @TED. It shows a Media Requests link (the real contact route for a media organisation) and a from: "description" link parsed out of the description on top of the structured widget:

{
  "channel": "TED",
  "channel_name": "TED",
  "emails": [],
  "email_count": 0,
  "links": [
    { "title": "Media Requests", "display": "media-requests.ted.com", "url": "https://media-requests.ted.com/", "from": "links_section" },
    { "title": null, "display": "www.ted.com/about/our-organization/our-policies-terms/ted-talks-usage-policy", "url": "https://www.ted.com/about/our-organization/our-policies-terms/ted-talks-usage-policy", "from": "description" }
  ],
  "links_count": 4,
  "source": "public_description",
  "reveal_available": false
}

Runnable: youtube_email_scraper_api_codes/contact.py


Qualify a list of YouTube channels by contact route

The job most people arrive with: a shortlist of channel handles (a sponsorship pipeline, a partner list, a competitor set) and a need for one row each with the contact route filled in: website, socials, and a public email where the creator published one.

export CHOCODATA_API_KEY="your_key"
python youtube_email_scraper_api_codes/qualify_channels.py

It reads channels.txt if present, one @handle or channel URL per line, otherwise runs the sample list. Four workers keep it inside both the concurrency cap and the 120 requests/60s limit, and it writes channels_contacts.csv with the website and link count per channel:

Qualifying a list of YouTube channels by contact route

Source: youtube_email_scraper_api_codes/qualify_channels.py


Measured latency

Ten consecutive calls to /youtube/email for the same channel, 2 seconds apart, on 2026-07-20. Nine returned 200 and are timed below; the sixth returned a retryable 502:

Measured latency for the YouTube email endpoint

Metric Value
calls attempted 10
200 responses 9
min 1,319 ms
median 2,866 ms
max 3,980 ms

Ten calls is a small sample. One returned a retryable 502, which is why a bulk run should retry once before giving up on a channel.


License

MIT. See LICENSE.