YouTube Channel Intel avatar

YouTube Channel Intel

Pricing

from $5.10 / 1,000 channel record delivereds

Go to Apify Store
YouTube Channel Intel

YouTube Channel Intel

Public intel card for any YouTube channel: subscribers, total views, video count, join date, country, full description, business email (if the author put one in the description), and every external link (site, Twitter/X, Instagram, Discord, ...). No API key, no login.

Pricing

from $5.10 / 1,000 channel record delivereds

Rating

0.0

(0)

Developer

Tim Zinin

Tim Zinin

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

2

Monthly active users

3 days ago

Last modified

Share

YouTube Channel Intel: The Public /about-Page Card for Any YouTube Channel

How this Actor turns your input into a decision Paste @handle, a full channel URL, or a bare UC... channel ID, and this Actor returns the same "About" card a human visitor sees on the channel's own page: subscriber/view/video counts, join date, country, the full description text, any business email the channel owner typed into that description, and every external link the channel lists — decoded to its real destination, never YouTube's redirect wrapper. No login. No API key. No comment, transcript or search scraping — those are the paths YouTube's own robots.txt closes; /about is not one of them, verified live below.

The billable outcome is a delivered channel record: a channel that resolved and whose card was actually read, whether or not it happened to publish an email — subscribers, description and links alone are useful for lead generation even with an empty emailsFound. A channel that does not resolve — a wrong handle, a deleted channel, or a syntactically valid but nonexistent ID — is always free, with a named, honest reason on the row, never charged.

What you get

  • Subscribers, total views, video count — YouTube's own displayed text, read from the one place on the page that actually belongs to the requested channel, not the six lookalike-widget numbers sitting right next to it under the same field name. See Evidence and boundaries for the exact trap this Actor was built to avoid.
  • Join date and country, when the channel owner has set them. null, not a guess, when they have not.
  • The full description text, exactly as the channel author wrote it — every paragraph, every line break, preserved verbatim. A real captured example below runs to five full paragraphs with no truncation applied by this Actor.
  • A business email, extracted only from that description text, if and only if the owner typed one in. Never from YouTube's separate, login-gated "Business inquiries" button, which this Actor does not and will not attempt to bypass. An empty emailsFound array on a channel that clearly resolved is the normal, honest outcome for most channels — verified live below on two real, active, high-subscriber channels that simply never put an email in their own copy.
  • Every external link the channel lists — site, Twitter/X, Instagram, Discord, Reddit, a second channel, whatever the owner added — with the real destination URL decoded out of YouTube's own youtube.com/redirect?...&q= wrapper, byte-for-byte verified against the raw page below.
  • An honest found:false with error:null for a channel that provably does not exist, distinguished from a genuine technical failure by a populated error string. Neither case is ever charged.
  • Runs on Apify: schedule it, call it from the REST API or an MCP client, export to JSON, CSV or Excel, or push results straight into a CRM or lead pipeline.

Where this Actor sits in your workflow

Who uses it

  • B2B lead-gen agencies, SDRs and marketers building an influencer or B2B-channel outreach list who need a business email, site or social handle for cold contact, without opening each channel's page by hand.
  • Partnership and sponsorship teams doing first-pass triage on a list of candidate channels before a human reviews each one — subscriber count and description alone are often enough to cut a list down before anyone opens a browser tab.
  • Researchers and analysts who need a channel's self-reported metadata (join date, country, description) captured as a stable, timestamped record rather than screenshotted by hand.
  • Developers and workflow builders who want a flat, 16-field JSON contract — found, error, emailsFound, externalLinks — that an agent or n8n node can branch on directly, without parsing YouTube's own HTML or handling its embedded JSON shape by hand.

How to run

  1. Open the Actor on Apify and click Try for free — no card required on the free plan.
  2. Paste channels into the YouTube-каналы (channels) field: @handle, a full channel URL (https://www.youtube.com/@handle, .../channel/UC..., .../c/<name>, .../user/<name>), or a bare UC... channel ID. One per line, up to 100 per run. The prefilled
    ["@mkbhd", "@ycombinator", "@semrush"]
    is a live, working default — running it as-is produces a real, non-empty, three-row result at no cost beyond the platform's own run-start charge.
  3. Optionally set maxConcurrency (default 5, range 1-20) — see Operating guide for why the default is deliberately conservative.
  4. Press Start, or launch the same run from the Apify API, a scheduled Task, or an MCP client.
  5. Read the Dataset — one row per requested channel, in the order they finished, not necessarily the order submitted, since channels are processed concurrently.
  6. Route every found:true row into your CRM or outreach list; treat every found:false row as a free, documented non-match — the error field tells you whether it was a confirmed absence or a real failure.
  7. Check this run's key-value store record OUTPUT for a one-time run summary — requested/delivered/ paid/free/failed/incomplete counts, replay safety and any fatal error — written once at the end of the run, alongside the Dataset rows.

There is no separate "test mode." Every run against a real channel produces real data; the difference between a free and a billed row is entirely found, decided by this Actor's own resolution logic, not by anything you configure.

Pricing

$0.005 per run start, plus $0.006 per delivered channel record. The delivered channel record is the one and only billable event on this page — internally metered as the platform event result-found, fired exactly once per row where found:true, inside the same atomic push-and-charge call that writes the row to the Dataset (see Evidence and boundaries for why that matters under concurrency).

This Actor's own cost of goods is close to zero — one HTTP GET of roughly 2.5-2.6MB per channel, parsed locally, no paid upstream API, no LLM, no proxy, no headless browser. The price is for the parsing, identity-verification and delivery guarantees described on this page, not a resold data bill.

A delivered channel record is billed the moment a channel resolves and its card is read — regardless of whether an email was found. A channel that fails to resolve, whether because it genuinely does not exist or because the input itself was rejected before any request was made, is always free, with a named reason on the row. A run of 20 channels where 14 resolve and 6 do not is billed for 14 delivered records plus one run start, never for the 6 that came back empty.

Input contract

  • channels (required, array of strings, up to 100 per run, editor: stringList) — @handle, a full youtube.com URL in any of four shapes (/@handle, /channel/UC..., /c/<name>, /user/<name>), or a bare UC... channel ID. This Actor normalizes the value itself; you do not need to pre-format it. Any element that looks like a URL (starts with http) but does not resolve to youtube.com, www.youtube.com or m.youtube.com is rejected before any network request is made — see the SSRF-guard example under Real happy, partial and failure output.
  • maxConcurrency (optional, integer, default 5, range 1-20) — how many channels this run reads at once. The default is conservative on purpose: live stability was verified at roughly one request per second, sequentially, ten times in a row with zero rate-limit responses — not at high sustained parallelism. Raising this is honored, but it is your own exposure to increase, not a limit this Actor has separately load-tested for you.
{
"channels": ["@mkbhd", "@ycombinator", "@semrush"]
}
{
"channels": [
"https://www.youtube.com/channel/UCBJycsmduvYEL83R_U4JriQ",
"https://www.youtube.com/user/marquesbrownlee",
"@this-handle-should-not-exist-zzz123"
],
"maxConcurrency": 10
}

(Both blocks are contract illustrations built from .actor/input_schema.json, not run output — an input has no result of its own to show. The second one deliberately mixes a bare channel ID, a legacy /user/ URL and a handle that does not exist, to show three of the four accepted input shapes in one call; every value in it was independently exercised in the real runs quoted below.)

Real happy, partial and failure output

Every row shares one flat, 16-field shape (input, found, channelId, handle, canonicalChannelUrl, title, subscriberCountText, viewCountText, videoCountText, joinedDateText, country, description, emailsFound, externalLinks, error, checkedAt) regardless of outcome — the exact field list main.js's buildRow() always returns, never a partial or extended shape depending on the path taken. Every JSON block below is copied verbatim from this Actor's own local runs (

apify run --purge
) against the real, live source, on 2026-08-17/18 — nothing here is constructed or filled in from memory.

Happy path — a resolved channel with a published business email

{
"input": "@mkbhd",
"found": true,
"channelId": "UCBJycsmduvYEL83R_U4JriQ",
"handle": "@mkbhd",
"canonicalChannelUrl": "http://www.youtube.com/@mkbhd",
"title": "Marques Brownlee",
"subscriberCountText": "21.1M subscribers",
"viewCountText": "5,533,853,712 views",
"videoCountText": "1,841 videos",
"joinedDateText": "Joined Mar 21, 2008",
"country": "United States",
"description": "MKBHD: Quality Tech Videos | YouTuber | Geek | Consumer Electronics | Tech Head | Internet Personality!\n\nbusiness@MKBHD.com\n\nNYC",
"emailsFound": ["business@MKBHD.com"],
"externalLinks": [
{ "title": "Twitter", "url": "http://twitter.com/MKBHD" },
{ "title": "Instagram", "url": "http://instagram.com/MKBHD" },
{ "title": "The Studio", "url": "http://youtube.com/c/TheStudio" },
{ "title": "Reddit", "url": "http://reddit.com/r/MKBHD" },
{ "title": "Discord", "url": "http://discord.gg/MKBHD" }
],
"error": null,
"checkedAt": "2026-08-17T20:22:35.100Z"
}

This is a billed row (found:true). Every field here was read from the real page — see Evidence and boundaries for the raw HTML this exact row was built from, including proof that the Twitter URL above was decoded out of a genuine youtube.com/redirect?...&q=http%3A%2F%2Ftwitter.com%2FMKBHD wrapper on the live page, not copied from the visible link text.

Happy path — a resolved channel with no email (an honest empty result, not a bug)

{
"input": "@ycombinator",
"found": true,
"channelId": "UCcefcZRL2oaA_uBNeo5UOWg",
"handle": "@ycombinator",
"canonicalChannelUrl": "http://www.youtube.com/@ycombinator",
"title": "Y Combinator",
"subscriberCountText": "2.35M subscribers",
"viewCountText": "105,903,453 views",
"videoCountText": "892 videos",
"joinedDateText": "Joined Oct 24, 2013",
"country": "United States",
"description": "All the world is changing around technology and you may contribute a line of code. What will yours be? \n\nSubscribe for startup advice, founder stories, and a look inside Y Combinator.\n\nWhat is Y Combinator?\nWe invest $500,000 in every startup and work intensively with the founders for three months. For the life of their company, founders have access to the most powerful community in the world, essential advice, later-stage funding and programs, recruiting resources, and exclusive deals. \n\nVisit ycombinator.com to learn more.\n\n",
"emailsFound": [],
"externalLinks": [
{ "title": "Apply to YC", "url": "http://ycombinator.com/apply" },
{ "title": "Subscribe to our newsletter", "url": "https://www.ycombinator.com/subscribe" }
],
"error": null,
"checkedAt": "2026-08-17T20:22:35.123Z"
}

This row is also billed (found:true, same price as the row above) — a 2.35-million-subscriber channel with a full description and two useful links resolved cleanly, it simply never published an email in its own copy. This is the exact case the task behind this page asked to be verified, not assumed: this row is a real, live result, run specifically to confirm that an empty emailsFound on an active, resolved channel is an honest outcome of the source, not a parsing failure. A second channel in the same batch (@semrush, 221K subscribers, a five-paragraph description, seven external links) resolved the same way with the same empty emailsFound — the pattern held twice, not once.

Confirmed-absence path — a handle that does not exist (HTTP 404)

{
"input": "@this-handle-should-not-exist-zzz123",
"found": false,
"channelId": null,
"handle": "@this-handle-should-not-exist-zzz123",
"canonicalChannelUrl": null,
"title": null,
"subscriberCountText": null,
"viewCountText": null,
"videoCountText": null,
"joinedDateText": null,
"country": null,
"description": null,
"emailsFound": [],
"externalLinks": [],
"error": null,
"checkedAt": "2026-08-17T20:22:52.424Z"
}

Free. error:null here is deliberate, not a bug: a curl request against the exact same URL (youtube.com/@this-handle-should-not-exist-zzz123/about) run independently of this Actor, at the same session, returned a plain 404 — a confirmed, unambiguous absence, not a technical failure. This Actor treats an HTTP 404 on /about as an honest negative and never charges for it.

Confirmed-absence path — a syntactically valid but nonexistent channel ID (HTTP 200, a different signal)

{
"input": "UCxxxxxxxxxxxxxxxxxxxxxx",
"found": false,
"channelId": null,
"handle": null,
"canonicalChannelUrl": null,
"title": null,
"subscriberCountText": null,
"viewCountText": null,
"videoCountText": null,
"joinedDateText": null,
"country": null,
"description": null,
"emailsFound": [],
"externalLinks": [],
"error": null,
"checkedAt": "2026-08-17T20:22:52.513Z"
}

Also free — but for a genuinely different reason, and this Actor checks for both. An independent curl against youtube.com/channel/UCxxxxxxxxxxxxxxxxxxxxxx/about, run in the same session, returned HTTP 200 with a 755,837-byte body containing the literal text "This channel does not exist" — a clean success status code, wrapping an honest negative in the body text instead of the status line. A status-code-only check would misread this as a live channel with an empty card. This Actor checks the body text for that exact phrase before attempting any JSON extraction, specifically so this case is never confused with a real parsing failure, and specifically so it is never billed.

Failure path — an input rejected before any request was made (SSRF/host-confusion guard)

{
"input": "https://evil.example/@fake",
"found": false,
"channelId": null,
"handle": null,
"canonicalChannelUrl": null,
"title": null,
"subscriberCountText": null,
"viewCountText": null,
"videoCountText": null,
"joinedDateText": null,
"country": null,
"description": null,
"emailsFound": [],
"externalLinks": [],
"error": "blocked host (not a youtube.com URL): evil.example",
"checkedAt": "2026-08-17T20:22:52.089Z"
}

Free, and structurally different from the two confirmed-absence rows above: error is populated here, because this row represents a real validation failure, not a source-confirmed negative. This Actor never sends a request to evil.example at all — the host is checked against an allow-list of exactly three YouTube hostnames before any URL is built, and a mismatch is rejected client-side. The error string always starts with blocked host for this specific case, a stable prefix a downstream workflow can match on.

Field dictionary

FieldMeaningImportant boundary
inputThe channel value exactly as submittedNever normalized or rewritten in this field, even when the request was
foundThe billing boundarytrue only on a resolved, delivered, billed channel record; false on every unresolved, error and invalid-input row
channelIdThe canonical UC... id YouTube itself assigns the channelnull on every unresolved row
handleThe @handle form, when the input was — or normalized to — a handlenull for bare channel IDs and for legacy /c///user/ paths, which carry no handle form
canonicalChannelUrlYouTube's own canonical URL for the resolved channelnull on every unresolved row; not guaranteed to echo the input's own URL shape
titleThe channel's display nameRead from the page's og:title meta tag specifically — see Evidence and boundaries for why not from the same JSON block as everything else
subscriberCountTextYouTube's own rounded display textNever an exact number — the source itself does not publish one. Read only from the channel's own record, never the six lookalike-widget values sharing the same field name on the same page — see Evidence and boundaries
viewCountTextSame rounded-text convention, total viewsnull when the source omits it
videoCountTextSame rounded-text convention, video countnull when the source omits it
joinedDateTextThe channel's self-reported join date, as text ("Joined Mar 21, 2008")null when the channel has not set it, or it is absent from the page
countryThe channel's self-reported countrynull when unset — never inferred from language, currency or any other signal
descriptionThe full "About" description text, exactly as authoredNo truncation applied by this Actor; preserved with original line breaks
emailsFoundEmails matched by a regex scan of description onlyCommonly empty — most channels never publish one there. Never sourced from the login-gated "Business inquiries" button, and never guessed
externalLinks{title, url} pairs for every link the channel listsurl is the real destination, decoded out of YouTube's youtube.com/redirect?...&q= wrapper when the source used one; passed through unchanged for the (also observed) direct-link case
errornull on both success and a confirmed-absence negative; populated only for a genuine technical or validation failureThis is what separates "the source confirmed this channel does not exist" from "something actually went wrong" — see Decision routing
checkedAtISO 8601 timestamp of when this row was producedShared granularity across a run, not per-request network latency

Evidence and boundaries

The subscriberCountText trap, measured with real numbers. On a single live fetch of @mkbhd's /about page, the literal key "subscriberCountText" appears seven times, in two different JSON shapes. Six are object-form,

{"accessibility":{"accessibilityData":{"label":"1.13 million subscribers"}},"simpleText":"1.13M subscribers"}
and five siblings reading 1.27M, 523K, 541K, 232K and 905K — every one of those belongs to the page's own "similar channels" recommendation widget, and every one of them is someone else's number. The seventh occurrence is string-form, "subscriberCountText":"21.1M subscribers", sitting inside the one JSON object (aboutChannelViewModel) that actually describes the requested channel. A parser that returns the first match of the key — the naive, obvious implementation — returns 1.13M at a clean HTTP 200: a confidently wrong number, worse than an empty result, delivered to a paying buyer as if it were fact. This Actor's extraction (resolveChannelFromAboutBody in lib/channel.js) reads exclusively the string-form occurrence inside aboutChannelViewModel, and that value is never re-derived anywhere else in the code path — it flows straight from the resolver into the row that gets billed. main.js's own comment on this exact line calls it out as the one field in this Actor that must never regress to a whole-page key search.

Identity, not position — because the page itself drifts. aboutChannelViewModel does not appear a fixed number of times on /about. A live fetch during this README's own verification found it exactly once; separate measurements recorded in this Actor's build evidence found it twice on the same kind of page. Selecting "the second occurrence" — a plausible-looking fixed rule — silently breaks and returns the wrong block the instant the page renders with only one. This Actor's rule is identity-based instead: every occurrence of aboutChannelViewModel is scored against the channel the buyer actually asked for (channelId for an ID-shaped input, the @handle segment parsed out of canonicalChannelUrl for a handle-shaped input), and the last confirmed match wins — never a match by position. A block that provably names a different channel is never eligible, even if it is the page's only block. This is covered by a dedicated golden in acceptance.json ([identity-guard, HUB10 gate blocker 17.08.2026]) that accepts either a fully-confirmed found:true with the exact right channelId, or a clean, honest found:false — and explicitly forbids the one outcome that must never happen: a confirmed channel carrying a mismatched identity.

Legacy /c/<name> and /user/<name> input — a real gap in the source, handled honestly. For a handle or a bare channel ID, the page carries a directly comparable field (canonicalChannelUrl's @handle segment, or channelId itself) that this Actor can check the resolved block's identity against. For the two legacy vanity-URL forms, no such field exists anywhere in aboutChannelViewModel — the old /c/ or /user/ name the buyer typed is simply not present on the page in any comparable shape. This Actor's rule for that specific gap: if the page carries exactly one aboutChannelViewModel occurrence, that occurrence is accepted — there is nothing to disambiguate it from. If the page carries two or more, and none of them can be positively confirmed against anything else, the request is refused rather than guessed. Both legacy forms were run live for this page: https://www.youtube.com/c/mkbhd and https://www.youtube.com/user/marquesbrownlee each resolved cleanly to

channelId: "UCBJycsmduvYEL83R_U4JriQ"
with handle:null (the field this Actor never fills in for a legacy-form request), because each page carried exactly one occurrence at the time of the check. The two-occurrence refusal branch itself is not something a live YouTube page can be made to reproduce on demand — it is proven by a dedicated offline unit test instead (lib/test/channel.test.js, "legacy /c/<name> request, 2 occurrences and neither is determinable -> ok:false"), part of a 16-test suite that passed 16/16 on the same run that produced the JSON in this section. This asymmetry — live-verified for the common case, unit-tested for the rare adversarial one — is stated here plainly rather than implied.

Two different negative signals, and this Actor checks both. A nonexistent handle and a syntactically-valid-but-nonexistent channel ID do not fail the same way. Independent curl checks run for this page confirm it directly: the handle case returns a plain HTTP 404; the channel-ID case returns HTTP 200 with the literal text "This channel does not exist" inside a 755,837-byte body. A check that only inspects the status code would treat the second case as a live channel with an empty card. This Actor's body-text check runs before any JSON extraction is attempted, so it always wins regardless of what the "not found" page's own markup happens to contain elsewhere — and both cases are recorded with error:null, the same honest-negative convention, never error-populated and never billed.

External links, decoded and verified against the raw page. The Twitter entry in the @mkbhd example above reads "url":"http://twitter.com/MKBHD". The raw HTML behind that same row, fetched independently for this verification, contains "url":"https://www.youtube.com/redirect?event=channel_description&redir_token=...&q=http%3A%2F%2Ftwitter.com%2FMKBHD" — the real destination is buried in the q= parameter of a YouTube-hosted redirect wrapper, percent- encoded. This Actor's decodeLinkUrl recognizes the wrapper by hostname and path (/redirect on a youtube.com host specifically, never any other host), pulls the q parameter through URLSearchParams (which percent-decodes automatically), and returns the real URL — which is exactly what the row above shows. Not every link on the same page uses the wrapper: "The Studio" resolves directly to "http://youtube.com/c/TheStudio" with no /redirect involved at all, and this Actor's decoder passes that case through unchanged rather than assuming every link needs unwrapping. Both shapes were directly observed on the same live page in the same fetch, and both are handled by the same function without a special case for either.

The business-email boundary, stated plainly. emailsFound is populated by one regex scan of description and nothing else. This Actor never follows a channel's own external links to check for a contact email on a linked site, never queries a third-party enrichment service, and never attempts to read YouTube's separate "Business inquiries" contact button, which requires a signed-in Google session to even reveal — bypassing a login wall is out of scope by design, not a missing feature. The two live rows in this page's "no email" example (@ycombinator, @semrush) are the direct, verified proof of this boundary in practice: two real, large, active channels, fully resolved, with real multi-paragraph descriptions, and a genuinely empty emailsFound — not a bug, not a null, an honest empty array, because neither channel put an email in its own copy.

Why title is read from og:title, not from the same JSON block as everything else. In the current page shape, aboutChannelViewModel — the object every other channel-identity field in this Actor comes from — carries no title key at all. A whole-page recursive search for a title key would risk exactly the same class of trap documented above for subscriberCountText: multiple unrelated title occurrences elsewhere on the page (the similar-channels widget included) sharing the same key name. The page's <meta property="og:title" content="..."> tag, by contrast, is a single, plain-HTML value with no duplicate or sibling occurrence on the page — a direct regex against it carries none of that ambiguity. title is therefore read from that one tag, independently of the JSON-extraction path everything else in this row uses, and is null on any page that never resolves to a real channel (the tag is simply absent).

Billing correctness under concurrency. chargeAndPush in lib/billing.js runs the budget check, the platform charge, and the Dataset write inside one mutex-guarded critical section — a serialized queue that survives a thrown exception in any single call rather than wedging. This closes the specific race a naive implementation invites: two channels resolving at once, both reading "budget remains" before either one spends it, both proceeding, and the buyer being charged for more than they authorized. The same guard also refuses to do any paid work at all if the platform's own pricing configuration is broken — for example, if Dataset writes themselves were accidentally priced, which would make even a free explanatory row cost money — and fails the run outright rather than silently delivering unbilled or over-billed results.

Robots.txt, checked directly, not assumed. www.youtube.com/robots.txt, fetched fresh for this page's own verification, disallows /api/, /comment, /live_chat, /login, /results, /timedtext_video, /youtubei/ and a handful of other paths — and nowhere in that list is /about, /channel/, /@<handle>, /c/ or /user/, the exact paths this Actor reads. /results (search) and /comment are the two paths that specifically close off a transcript-and-comment-scraping design for this Actor's own product line — named directly in this Actor's own "What this Actor does NOT do" boundary, not inferred from silence.

Decision routing

Row shapeBillable?errorWhat it means
Resolved channel, card readYesnullThe one billable outcome — found:true, whether or not emailsFound is empty
Handle does not exist (HTTP 404)NonullA confirmed, unambiguous negative straight from the source's own status code
Syntactically valid ID, channel does not exist (HTTP 200 + body text)NonullA confirmed negative wrapped in a success status — the second negative signal this Actor checks explicitly
Blocked/invalid input (SSRF guard)No"blocked host (...)" or similar, always populatedA real validation failure; no request was ever sent
Response truncated at the byte capNo"response truncated at the byte cap on /about ..."The page exceeded this Actor's 4.5MB safety ceiling before finishing; not observed on any real page measured for this Actor (real pages run ~2.5-2.6MB), disclosed as a code path, not a live-reproduced case
aboutChannelViewModel absent or unresolvable identityNoPopulated, names the specific failureA genuine parsing/shape failure, or a legacy-URL request with two or more undeterminable occurrences — never confused with a confirmed absence
Network error / timeoutNoPopulated with the underlying causeA real technical failure, distinguishable from a confirmed absence by error being non-null
Run's own budget exhausted mid-runNoA free explanatory row is written naming the stopNothing already billed is affected; the remaining channels in that run are simply not attempted

A workflow only needs two rules: act on found === true; and read error === null on a found:false row as "the source confirmed this channel does not exist," never as "something went wrong" — that distinction is exactly what error being populated or not is for.

Commercial playbooks

Influencer and creator-partnership sourcing

A partnerships team feeds a candidate list of channel handles gathered from a niche search or a competitor's sponsor reel into channels, and reads subscriberCountText, description and emailsFound for a fast first-pass filter before anyone opens YouTube by hand — subscribers and description alone are often enough to cut a list of fifty down to the ten worth a human look, and the handful with a published email skip straight to outreach.

B2B channel and thought-leader lead lists

An SDR building a list of B2B YouTube channels in a given space (SaaS, fintech, dev tools) runs a batch of known or discovered handles, keeps the rows with a real emailsFound entry for direct outreach, and keeps the rows without one — externalLinks almost always includes the channel's own site — for a site-contact-form or LinkedIn-based approach instead.

Periodic channel-health monitoring

A brand tracking its own or a partner's channel schedules a run against a small, fixed list of handles on a recurring cadence, watching subscriberCountText, videoCountText and description drift over time without manually revisiting the channel page — each run is a real, timestamped snapshot, not a cached estimate.

Integration recipes

Scheduled batch enrichment

An Apify Task runs this Actor on a fixed list of channels weekly, appending new Dataset rows each time; downstream automation reads found, emailsFound and externalLinks and routes only the rows with a real email into an outreach sequence, leaving the rest in a "no direct contact found" bucket for a different approach.

Agent/MCP pattern

An agent supplies a list of handles or URLs discovered from another source (a search result, a competitor's collaborator list), reads found and error before doing anything else with a row, and never treats an empty emailsFound on a found:true row as a failure worth retrying — retrying does not change what the channel's own description says.

CRM/spreadsheet append pattern

Dataset rows are appended keyed by channelId when found:true (stable across re-runs of the same channel), or by input + checkedAt for an unresolved row (which carries no channelId); null is preserved as null rather than coerced to an empty string, so a downstream query can tell "the channel never set this field" apart from "this field is genuinely blank text."

Operating guide

Why maxConcurrency defaults to 5, not higher. Live stability for this source was verified at roughly one request per second, sequentially, ten requests in a row with zero rate-limit or blocking responses. That measurement supports a modest default with headroom; it does not, on its own, prove stability at high sustained parallelism across a hundred-channel run. The 1-20 range is honored if you raise it, but doing so is your own exposure to increase, based on your own measured tolerance for the channels you actually run — not a ceiling this Actor's own build evidence independently validated at the high end.

Why the input cap is 100, enforced twice. .actor/input_schema.json declares maxItems:100 for buyer-facing validation in the Apify Console, but that schema check is advisory — an API caller can bypass client-side schema validation entirely. main.js re-enforces the same 100-channel ceiling in code, after deduplicating the input list, specifically so an API-driven run cannot exceed it by skipping the Console form.

Reading an empty emailsFound. This is the normal outcome for most channels, not a sign of a broken run — both live examples in Real happy, partial and failure output demonstrate it directly on large, active, fully-resolved channels. Re-running the same channel will not change the result; the description text is what it is.

Choosing an input shape. A bare UC... channel ID or an @handle gives this Actor a directly comparable identity field to verify the resolved page against. A legacy /c/<name> or /user/<name> URL does not — see Evidence and boundaries for exactly what that means for those two input shapes, and why they are refused rather than guessed in the rare case the page carries more than one ambiguous block.

Concurrency and row order. Dataset rows are written in completion order, not submission order, when maxConcurrency is greater than 1. A downstream process that needs to preserve the original request order should sort on input after the run, not assume row order matches the input array.

Related tools for adjacent workflows in B2B lead generation and data enrichment, social platforms and regional directories.

ActorWhat it does
Telegram Export IntelligencePair it in the social platforms and regional directories workflow: Turn owner-authorized Telegram message exports into per-post reach context, evidence, confidence, review...
Authorized Professional Directory IntelligencePair it in the social platforms and regional directories workflow: Turn buyer-authorized or licensed professional directory exports into privacy-aware evidence, freshness,...
YouTube Channel Videos ListPair it in the social platforms and regional directories workflow: List up to 100 most recent uploads for any YouTube channel: videoId, title, view count, and publish age...
Company Hiring RadarPair it in the B2B lead generation and data enrichment workflow: Turn buyer-supplied Greenhouse, Lever, or Ashby board tokens into evidence-backed public hiring...
Competitor Change Monitor — Tech, Hiring & FilingsPair it in the B2B lead generation and data enrichment workflow: Monitor one competitor domain for technology, hiring, and SEC-name changes with explicit baselines,...

FAQ

Why is emailsFound empty for a real, active, high-subscriber channel?

Most channels never put an email into their description at all. YouTube's separate "Business inquiries" button is gated behind a Google login this Actor does not and will not bypass. An empty array means exactly that — no email in the free text, nothing more — and two of the real rows on this page (@ycombinator, @semrush) exist specifically to demonstrate that this is the normal case, not a defect.

Why is title sometimes null?

It is read from the page's og:title meta tag, which is only present on pages that actually resolve to a real channel. A found:false row never carries a title.

Does this include the channel's video list, or similar/lookalike channels?

No. This Actor is the contact/description/links card only. A companion Actor in the same fleet, YouTube Channel Videos List, covers the channel's own uploads; another, YouTube Channel Lookalike Finder, covers similar-channel discovery — the exact widget whose numbers this Actor deliberately excludes from subscriberCountText (see Evidence and boundaries).

Why does a legacy /c/<name> or /user/<name> URL sometimes get refused when a handle for the same channel would succeed?

Because the page carries no field comparable to that old vanity name — nothing to positively confirm the resolved block actually belongs to the channel you typed. When the page carries exactly one candidate block, it is accepted; when it carries two or more and neither can be confirmed, this Actor refuses rather than guesses. A handle or a bare channel ID does not have this gap, because the page does carry a directly comparable field for both of those input shapes.

Can the same channel ever be billed twice for one run?

No — each requested channel produces exactly one row, and the charge for that row happens once, inside the same atomic operation that writes it to the Dataset (see Evidence and boundaries, "Billing correctness under concurrency").

What happens if I submit the same channel twice in one run's channels list?

Duplicate values are deduplicated before processing (main.js builds the run's channel list through a Set), so a repeated value in your input produces one row and one charge, not two.

Is reading /about actually allowed under YouTube's own rules?

robots.txt, checked directly for this page, does not disallow /about, /channel/, /@<handle>, /c/ or /user/ — only /api/, /comment, /results, /login and a specific list of other paths, none of which this Actor touches. See Sources and rights for the exact quoted lines and what that boundary does and does not mean for your own use of the resulting data.

Does this Actor guess or infer anything the source itself did not publish?

No. Every field is either read directly from the page or set to null. subscriberCountText, viewCountText and videoCountText are the source's own rounded display text, never converted to a fabricated exact number; a missing country or joinedDateText stays null, never inferred from any other signal on the page.

Sources and rights

Source family: YouTube public channel and playlist pages. This Actor reads exactly one of them — the channel's own About page — anonymously, with no login, no API key and no browser session.

The only source. The channel's own public /about page, served directly by www.youtube.comhttps://www.youtube.com/@<handle>/about or https://www.youtube.com/channel/<id>/about, with ?hl=en&gl=US and a consent cookie set on every request so the page renders in English regardless of the runner's own IP location. No YouTube Data API. No API key. No account, login or session of any kind.

robots.txt, quoted verbatim. Fetched fresh for this page's own verification:

User-agent: *
Disallow: /api/
Disallow: /channel_picker
Disallow: /comment
Disallow: /feeds/videos.xml
Disallow: /file_download
Disallow: /get_video
Disallow: /get_video_info
Disallow: /get_midroll_info
Disallow: /live_chat
Disallow: /login
Disallow: /qr
Disallow: /results
Disallow: /signup
Disallow: /t/terms
Disallow: /timedtext_video
Disallow: /verify_age
Disallow: /watch_ajax
Disallow: /watch_fragments_ajax
Disallow: /watch_popup
Disallow: /watch_queue_ajax
Disallow: /youtubei/

None of the paths this Actor reads — /about, /channel/, /@<handle>, /c/, /user/ — appear in this list. /results (search) and /comment do, which is exactly why this Actor never scrapes search results or comments, and never will under this design.

What this is not. Unlike UK government open data published under an explicit reuse licence, YouTube publishes no equivalent commercial-redistribution grant for this page's content, and robots.txt permission to fetch a path is not the same thing as a licence to redistribute what it contains. This Actor reads a page that is publicly served with no login wall and not disallowed by the site's own crawl policy; how you store, use and redistribute the resulting data — including any business email a channel owner chose to publish — remains your own responsibility under YouTube's Terms of Service and applicable data-protection law in your jurisdiction. This page makes no licensing claim on your behalf, and no claim that this use is risk-free for every downstream purpose.

Personal data note. A business email captured by this Actor is one the channel owner voluntarily and publicly published in their own channel description, specifically for the purpose of being contacted — the same status as a business email published on a company's own "Contact us" page. It is not extracted from a private field, a third-party leak, or any login-gated source.

Limits

  • No transcripts, no comments, no search results — /results and /comment are robots.txt-disallowed and this Actor never touches either path.
  • Subscriber, view and video counts are YouTube's own rounded display text ("21.1M subscribers"), never an exact number — the source itself does not publish one, so this Actor does not invent one.
  • Business email extraction is a regex scan of description text only. It never follows a channel's external links, never queries a third-party enrichment service, and never attempts to read YouTube's own login-gated "Business inquiries" field. An empty emailsFound on a fully resolved channel is a normal, expected outcome, verified live twice on this page.
  • Legacy /c/<name> and /user/<name> input URLs cannot be identity-verified the way a handle or a bare channel ID can — the page carries no comparable field for either legacy form. A single-candidate page is accepted; two or more undeterminable candidates are refused rather than guessed.
  • Video lists and similar/lookalike channels are out of scope for this specific Actor — see companion Actors named in the FAQ.
  • maxConcurrency's conservative default (5) reflects sequential-load verification, not independently validated high-parallelism stability; raising it toward the allowed maximum (20) is honored but is your own exposure to increase.
  • The 100-channel-per-run cap is enforced in code as well as in the input schema, so it holds even for an API-driven run that bypasses Console-side schema validation.
  • A response exceeding the 4.5MB per-page byte cap is reported as a free, honest truncated error rather than parsed partially and silently — real measured page sizes for this source run roughly 2.5-2.6MB, comfortably under that ceiling, so this path is a disclosed safety margin, not a case reproduced on a live page for this README.
  • This Actor depends on YouTube's current page structure (aboutChannelViewModel, og:title, channelExternalLinkViewModel). A structural change on YouTube's side could break extraction; this Actor is built to fail safe when that happens — an honest error, never a silently wrong field — but cannot guarantee YouTube will not change its own page shape.

Support boundary

Actor support covers this page's documented input validation, the resolution and identity-verification logic described above, the Dataset schema, and billing correctness for a delivered channel record on an accepted release. It cannot guarantee YouTube's own page structure, uptime, or continued availability of any specific field — this Actor reports what the source publishes, and stops honestly when the source publishes something this build does not recognize. It cannot verify that a captured business email is still monitored, still belongs to the same person, or will ever be answered. It cannot recover data YouTube itself never published (an exact subscriber count, a hidden or login-gated email, a channel's video list or its similar-channel recommendations — see companion Actors in the FAQ). When reporting a problem, include the Actor run ID, the specific input value involved, the full row's error text if any, and the approximate checkedAt time.


Built by zinin. Questions? Telegram @timzinin.