YouTube Channel API — Videos, Shorts & Search avatar

YouTube Channel API — Videos, Shorts & Search

Pricing

from $0.30 / 1,000 result returneds

Go to Apify Store
YouTube Channel API — Videos, Shorts & Search

YouTube Channel API — Videos, Shorts & Search

Scrape any YouTube channel's uploads as data: one row per video with title, view count, duration, publish date and thumbnail, plus a profile row with subscriber count, exact total views and join date. Also searches YouTube for videos, channels or playlists. Residential proxy included.

Pricing

from $0.30 / 1,000 result returneds

Rating

0.0

(0)

Developer

Insight Solutions

Insight Solutions

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

13 hours ago

Last modified

Share

YouTube Channel API — Videos & Search

Get a YouTube channel's uploads as data. Give this Actor a handle — @TED — and get back one row per video: title, view count, duration, when it went up, the thumbnail, the link. Ahead of them comes one profile row for the channel itself: subscribers, exact lifetime views, the day it was created, the country, the description, the uploader's own links. Switch a field and you get the Shorts grid or the live-streams tab instead. Add a search query and you get YouTube search results in the same shape.

No API key. No login. No cookies to paste. No YouTube Data API quota. $0.50 per 1,000 rows, residential proxy included, channels that do not exist are free, and a run that returns nothing costs nothing at all.

Try it in 30 seconds

{
"channels": ["@TED"],
"tab": "videos",
"maxVideosPerChannel": 30,
"includeChannelProfile": true
}

A handle, a channel URL, a /channel/UC… URL or a bare UC… ID — all four work, and so do the old /c/ and /user/ links.

What comes back

{
"ok": true,
"rowType": "video", // "video" | "channel" | "playlist" | "diagnostic"
"input": "@TED",
"tab": "videos", // channel mode
"query": null, // search mode
"position": 2, // 1-based, per channel or per query
"videoId": "1r2fqbD0KZ4",
"url": "https://www.youtube.com/watch?v=1r2fqbD0KZ4",
"title": "The Secret to Deeper Conversations | TED Intersections",
"channelId": "UCAuUUnT6oDeKwE6v1NGQxug",
"channelName": "TED",
"channelUrl": "https://www.youtube.com/channel/UCAuUUnT6oDeKwE6v1NGQxug",
"handle": "@TED",
"viewCount": 51000, // expanded from "51K views" — approximate above 1,000
"viewCountText": "51K views", // the string YouTube actually rendered
"publishedTimeText": "23 hours ago",
"publishedAtApprox": "2026-09-08T13:00:00.000Z",
"durationSec": 1687,
"durationText": "28:07",
"isLive": false,
"isShort": false,
"isUpcoming": false,
"isVerified": true, // the uploader's verification tick
"thumbnailUrl": "https://i.ytimg.com/vi/1r2fqbD0KZ4/hq720_custom_3.jpg",
"badges": [],
"error": null,
"errorType": null,
"scrapedAt": "2026-09-09T12:00:00.000Z",
"source": "youtube.com",
"sourceUrl": "https://www.youtube.com/@TED/videos"
}

The channel row that comes first carries the same columns with the channel ones filled in:

{
"ok": true,
"rowType": "channel",
"channelId": "UCAuUUnT6oDeKwE6v1NGQxug",
"title": "TED",
"handle": "@TED",
"subscriberCount": 27800000, // from "27.8M subscribers" — approximate, as YouTube publishes it
"videoCount": 5800, // exact, from the About panel ("5,800 videos")
"viewCount": 3224922765, // exact lifetime views — the one number YouTube does not round
"joinedAt": "2006-12-06", // a real date, not an approximation
"country": "United States",
"keywords": ["TED", "talks", "TED Conferences", "…"],
"links": [{ "title": "TED.com", "url": "https://www.ted.com" }],
"avatarUrl": "https://yt3.googleusercontent.com/…=s900-c-k-c0x00ffffff-no-rj",
"bannerUrl": "https://yt3.googleusercontent.com/…=w2560-fcrop64=1,00005a57ffffa5a8"
}

Every row in the dataset has the same keys, so it loads into a table without a schema fight.

Use cases

  • Competitor and category tracking — every upload from a set of channels, with views and dates, refreshed on a schedule. Diff two runs and you have "what they published this week and how it performed".
  • Content research and topic mining — a channel's whole back catalogue as titles plus view counts is the cheapest signal there is for what an audience actually watches.
  • Creator discovery and outreach lists — search channels for a niche and get handle, subscriber count and description snippet per row, ready to score and filter.
  • Sponsorship and influencer vetting — subscriber count, upload cadence, view distribution and the join date, in one place, for a list of candidates.
  • Trend monitoring — a video search with uploadDate: "week" on a schedule surfaces what is new on a topic before it is obvious.
  • Feeding an index or a RAG pipeline — video IDs and titles from a channel, handed to the YouTube Transcript API for the words.

How it works, and why it keeps working

YouTube's public Data API needs a Google Cloud project and an API key, spends quota per call, and caps a channel walk at what your quota allows. This Actor reads the same pages the browser reads, through the private InnerTube endpoints the YouTube web client itself calls.

StepRequestWhat it gets
1navigation/resolve_url with the handleThe UC… channel ID. Skipped entirely when you pass a UC… ID
2browse with {browseId, params}The channel document: profile header, and the first thirty uploads of the chosen tab
2bThe public channel page, only if step 2 returns no gridThe same tree from ytInitialData; survives some API-level refusals
3browse with the About continuationExact view count, join date, country, links — only when includeChannelProfile is on
4…browse with each next tokenThirty more uploads each time, until your cap, your budget, or the end of the grid
Ssearch with {query, params}Twenty results, plus the token for the page after

Two details in there are the difference between rows and empty output:

  • The channel grid is lockupViewModel, not gridVideoRenderer. YouTube moved the channel tabs to view models; scrapers written against the old renderer return nothing on a modern channel page. Shorts use a third shape again (shortsLockupViewModel) with no duration and no date in it at all.
  • A metadata row is a bag, not a tuple. "51K views" and "23 hours ago" arrive as an unordered list of parts, and a collaboration quietly adds a "TED and 2 more" part in front of them. This Actor classifies each part by what it says, so a collaboration does not become the view count.

Underneath: Apify residential proxy, one pinned session per parallel worker. When YouTube refuses an exit IP — HTTP 429, HTTP 403, an empty body, or a body that is not JSON — that session is retired and the same page is asked for once more from a different residential address. Retrying on an address that was just refused only deepens the block, so it is never done. If the second address is refused too, the walk stops, keeps every row it already delivered, and files one free blocked row saying where it stopped.

Pages of the same channel are spaced 250–600 ms apart. Nothing forces that; it is the difference between reading a channel and hammering one.

How it compares

  • No API key, no quota. The YouTube Data API needs a Google Cloud project and spends quota per call. This needs a handle.
  • The exact numbers, where exact numbers exist. Most scrapers report "27.8M subscribers" and stop. This one also fetches the About panel, so viewCount is the real lifetime figure, videoCount is "5,800" rather than "5.8K", and joinedAt is a date rather than a guess.
  • One shape for channels and for search. The same 44 columns whether the row is a video from a channel tab, a video from a search, a channel result or a playlist. Null where a field does not apply, never a missing key.
  • Residential proxy is in the price. YouTube blocks datacenter IPs wholesale, so a channel scraper without residential egress does not work from a cloud host at all. There is no separate proxy line on your bill for this Actor.
  • Failures are free and legible. A handle that does not exist, an empty tab, a search with no matches or a block produce a diagnostic row with an errorType you can branch on — and no charge. A run that returns nothing at all finishes FAILED with the reason in its status message, never a green run containing an apology.
  • A partial walk is kept, not thrown away. Hit maxRunSecs or your charge ceiling on page 8 and you keep pages 1–7.

Input reference

FieldTypeDefaultWhat it does
channelsarray of stringsprefilled with @TEDHandles, channel URLs, /c/ and /user/ URLs, or bare UC… IDs. Duplicates are read, and billed, once
tabvideos | shorts | livevideosWhich upload grid to read, newest first
maxVideosPerChannelinteger100Videos per channel. YouTube serves thirty at a time (about fifty for Shorts), so the walk stops on the page that reaches your number. 0 = the whole tab
includeChannelProfilebooleantrueWrite the channel row. Costs one extra request and bills as one row
searchQueriesarray of strings[]Optional. One entry per YouTube search. Both modes can run in one job
searchTypevideos | channels | playlistsvideosWhich of YouTube's own result filters to apply
uploadDateany | hour | today | week | month | yearanyYouTube's upload-date filter. Applies to a video search only, exactly as on the site
maxResultsPerQueryinteger50Results per query. YouTube serves twenty per page
maxConcurrencyinteger3Channels and queries in parallel. Each worker keeps its own proxy session. Pages within one target cannot be parallelised
maxRunSecsinteger240Whole-run wall-clock budget. When it runs out the Actor keeps what it has and files a free diagnostic row for each entry it never reached
proxyConfigurationobjectApify residentialLeave it alone. Clearing it sends requests from the run's datacenter address, which YouTube will block

Output reference

Every row carries the same keys. ok: true is a result; ok: false is a free diagnostic row.

FieldWhat it is
rowTypevideo, channel, playlist or diagnostic
input, query, tab, positionWhich entry produced this row, and where it fell in that entry's order
videoId, playlistId, channelId, urlThe IDs, and the canonical public link
title, channelName, channelUrl, handleWhat it is called and whose it is
description, descriptionSnippet, keywords, linksThe channel's own text (channel rows) and the snippet YouTube shows under a search result
viewCount, viewCountTextViews as an integer, and as YouTube rendered them
subscriberCount, subscriberCountText, videoCount, videoCountTextChannel and playlist sizes, both ways
publishedTimeText, publishedAtApproxWhen it went up, as YouTube says it and as a sortable instant
joinedAt, countryChannel rows: the day the channel was created, and the country the uploader declared
durationSec, durationTextVideo length, both ways
isLive, isShort, isUpcoming, isVerifiedLive right now; a Short; a scheduled premiere; the channel's verification tick
thumbnailUrl, avatarUrl, bannerUrl, badgesThe images, and the labels YouTube prints on the result
estimatedResultsSearch rows: how many results YouTube claims for the query
ok, error, errorTypeWhether this row is a result, and if not, why not
scrapedAt, source, sourceUrlWhen, and from which public page

errorType on a diagnostic row is one of:

ValueMeaningCharged?
not-foundNo such handle or channel — YouTube said soNo
no-resultsThe tab is empty, or the search matched nothing of the requested typeNo
unavailableThe channel could not be read at all: terminated, region-blocked, or a shape we do not recogniseNo
blockedYouTube refused our requests from two different residential exits. Rows already returned for that channel are keptNo
invalid-inputThe entry was not a channel reference or a usable queryNo
timeoutThe run's maxRunSecs budget ran out before this entry was reachedNo

Pricing

$0.50 per 1,000 rows. Pay-per-event, with the residential proxy already inside that number — there is no separate proxy line on your bill for this Actor.

EventWhat triggers itFREEStarterScaleBusiness
Result returned (primary)One video, channel or playlist row written to your dataset$0.0005$0.0005$0.0004$0.0003
Run startedOnce per run, after the first result$0.001$0.001$0.001$0.001

Worked example. 20 channels at 100 videos each, with profile rows, of which 2 handles no longer exist:

  • 18 channels × (100 videos + 1 profile row) × $0.0005 = $0.909
  • 1 run start = $0.001
  • 2 handles that do not exist = $0.00
  • Total: $0.91

What you are never charged for: a handle that does not exist, an empty tab, a search that matched nothing, an entry that was not a channel, an entry the run never reached before maxRunSecs, or a page YouTube blocked. If a whole run comes back empty it finishes FAILED and bills nothing at all, start fee included.

Set ACTOR_MAX_TOTAL_CHARGE_USD on a run and the Actor stops walking once the ceiling is in sight, rather than handing you rows it cannot bill or billing you for rows it cannot hand over. It finishes SUCCEEDED with the ceiling named in its status message, and everything already delivered is yours.

Limits, and the ones that might bite

View counts on a channel grid are approximate; on a search result they are exact. That is YouTube's doing, not ours: the channel grid renders "51K views" and publishes nothing more precise, while a search result carries "1,117,768 views" next to its abbreviated form. Both arrive in viewCount, and viewCountText always shows which one you got. Below 1,000 both are exact.

Subscriber counts are always approximate. "27.8M subscribers" is the entire truth available over any interface, this Actor's included. The About panel does not publish an exact one either. viewCount and videoCount on a channel row are exact, because the About panel publishes those.

Upload dates are relative. A grid publishes "23 hours ago" and a search result "4 years ago"; there is no absolute timestamp behind either. publishedAtApprox resolves the phrase against scrapedAt, stepping the calendar for months and years rather than multiplying by an average length — but "1 year ago" still covers twelve months of possible dates. Use it to sort and bucket, not to timestamp an event. joinedAt on a channel row is the exception: that one is a real date.

Shorts carry less. The Shorts grid publishes a title and a view count and nothing else — no duration, no date. Those columns are null, rather than guessed.

Search will not page forever. estimatedResults says two million; YouTube stops handing out continuation tokens long before that, usually a few hundred results in. Ask for what you will actually read.

No members-only or private content. This Actor does not log in, does not accept cookies and does not take a session token, and it never will — that is a deliberate line, not a missing feature. Unlisted videos never appear in a channel tab, and neither do videos removed for the region the proxy exits from.

One tab per run. tab picks one of videos, shorts and live. Run the Actor three times, or three schedules, if you want all three — they are three different grids and three different walks.

YouTube may change the format. This reads private endpoints that YouTube changes without notice — that is true of every tool that reads YouTube channels, including the ones that do not say so. When a shape changes, rows stop arriving and you get free blocked or unavailable diagnostic rows rather than quietly wrong data, and a run that returns nothing bills nothing.

Rate and reliability. Requests go out through residential addresses with per-worker sessions, one rotation per block, and a 250–600 ms pause between pages of the same target. Three targets in parallel is the default because it is where throughput and block rate balance; raising maxConcurrency speeds a long list up and makes blocks more likely.

Use it from an AI agent, or from code

One JSON object in, one flat array out — the shape agent runtimes want. The Actor runs with limited permissions, uses pay-per-event pricing and never enters Standby, so it works over the Apify MCP server and with x402 agentic payments. The Integrations tab pushes results to Slack, a webhook, Zapier, Make, Google Sheets, Snowflake or BigQuery.

curl -X POST "https://api.apify.com/v2/acts/insight.solutions~youtube-channel-api/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"channels":["@TED","@NASA"],"tab":"videos","maxVideosPerChannel":50}'
# pip install apify-client
from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("insight.solutions/youtube-channel-api").call(run_input={
"channels": ["@TED"],
"tab": "videos",
"maxVideosPerChannel": 200,
"includeChannelProfile": True,
"searchQueries": ["machine learning tutorial"],
"searchType": "videos",
"uploadDate": "month",
"maxResultsPerQuery": 40,
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
if not row.get("ok"):
print("skipped:", row["input"], row["errorType"])
elif row["rowType"] == "channel":
print(f'{row["title"]}: {row["subscriberCount"]:,} subs, {row["viewCount"]:,} views since {row["joinedAt"]}')
else:
print(f'{row["position"]:>3}. {row["title"][:60]}{row["viewCountText"]}, {row["publishedTimeText"]}')

FAQ

How do I get every video a channel ever published? Set maxVideosPerChannel: 0 and raise maxRunSecs. A channel with 5,800 uploads is about 195 sequential page requests with a pause between each — plan for minutes, not seconds, and bound it with ACTOR_MAX_TOTAL_CHARGE_USD if you are not sure what you are asking for.

Why is the profile row charged? Because it is a row, and it costs an extra request: the exact view count, join date, country and links live in the About panel, which is a separate continuation. Turn includeChannelProfile off and you pay for videos only.

Does the order match the channel page? Yes — newest first, which is YouTube's default for all three tabs. position records the order this run read them in.

Can I get a channel's playlists, community posts or podcasts? Not yet. playlist rows come from a playlist search. The channel's own Playlists, Posts and Podcasts tabs are different shapes again.

Why do some rows have no duration? They are Shorts, or a stream that was live when it was read. YouTube publishes no duration for either.

Do I need my own proxy or an API key? Neither. Apify residential proxy is configured by default and its cost is inside the per-row price. No Google Cloud project, no YouTube Data API key, no quota.

What happens if one channel fails? The others still run. The failed one produces a free diagnostic row and the run finishes SUCCEEDED. If every entry fails, the run finishes FAILED and you are billed nothing at all.

Is the data fresh? Live. Every run reads YouTube at that moment; nothing is cached.

  • Public pages only. Every source is a public channel page or a public search results page. The Actor never logs in, never accepts cookies or session tokens, never takes an API key belonging to anyone else, and never touches private, unlisted or members-only content.
  • Channel data can be personal data. A channel row carries a name, a handle, an avatar and sometimes a country, and for an individual creator that is personal data about an identifiable person under the GDPR and similar laws. You are the controller of whatever you collect: have a lawful basis, keep only what you need, honour deletion requests, and remember that a channel deleted on YouTube stays in your dataset until you remove it.
  • Titles and descriptions are their authors' words. Republishing them, or training on them, is your call and your responsibility, subject to YouTube's terms and to the law where you operate. Aggregation, ranking and analysis are the ordinary uses and are what this is built for.
  • Not affiliated with YouTube, Google LLC, or with any channel or creator whose content you retrieve. All product names and trademarks belong to their respective owners and are used only to describe which public endpoints this Actor reads.

Our other Actors

Every Insight Solutions Actor is pay-per-result with no browser, no login and no API key, and every one of them returns free diagnostic rows instead of billing for failures. Prices are per 1,000 results.

Video, audio & social

News, documents & the web

Business, finance & jobs

Apps & games