YouTube Comments API — Comments & Replies with Likes
Pricing
from $0.21 / 1,000 comment returneds
YouTube Comments API — Comments & Replies with Likes
Scrape YouTube comments as data. One row per comment: text, author, like count, reply count, relative and approximate absolute time, pinned and hearted flags. Sort by top or newest, walk reply threads, cap per video. Residential proxy included, no API key, no login.
Pricing
from $0.21 / 1,000 comment returneds
Rating
0.0
(0)
Developer
Insight Solutions
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
12 hours ago
Last modified
Categories
Share
YouTube Comments API — Comments & Replies
Get a YouTube video's comment section as data. Give this Actor a list of YouTube links — or bare video IDs — and get back one row per comment: the text exactly as posted, who wrote it, how many likes and replies it has, when it went up, and whether the creator pinned or hearted it. Ask for replies and each thread comes back too, threaded in behind the comment it answers.
No API key. No login. No cookies to paste. No YouTube Data API quota. $0.35 per 1,000 comments, residential proxy included, videos with comments turned off are free, and a run that returns no comments costs nothing at all.
Try it in 30 seconds
{"videoUrls": ["https://www.youtube.com/watch?v=iG9CE55wbtY","dQw4w9WgXcQ"],"maxCommentsPerVideo": 100,"sort": "top"}
One watch URL, one bare video ID — both work, and so do youtu.be share links, Shorts, embeds and live URLs.
What comes back
{"ok": true,"rowType": "comment", // "comment" | "reply" | "diagnostic""videoId": "iG9CE55wbtY","videoUrl": "https://www.youtube.com/watch?v=iG9CE55wbtY","videoTitle": "Do schools kill creativity? | Sir Ken Robinson | TED","commentId": "UgwtA0zH0G1Lmsp4l-Z4AaABAg","parentCommentId": null, // set on a reply, naming its thread"text": "Loved this quote:\n\"If you're not prepared to be wrong, you'll never come up with anything original\"\n- Sir Ken Robinson","authorName": "@obalfaqih","authorChannelId": "UC0BI507DZAOVYLEJ0617xDQ","authorUrl": "https://www.youtube.com/@obalfaqih","authorAvatarUrl": "https://yt3.ggpht.com/k1P8GMwE…=s88-c-k-c0x00ffffff-no-rj","authorIsCreator": false, // written by the channel that uploaded the video"authorIsVerified": false,"authorIsArtist": false, // Official Artist Channel badge"likeCount": 6100, // expanded from "6.1K" — approximate above 1,000"likeCountText": "6.1K", // the string YouTube actually rendered"replyCount": 26,"publishedTimeText": "7 years ago", // the only timestamp YouTube publishes"publishedAtApprox": "2019-09-09T05:53:05.487Z", // resolved against scrapedAt"isEdited": false,"isPinned": false,"isHearted": false, // ❤ from the creator"sortOrder": "top","position": 1, // 1-based, in the order this run walked the video"totalCommentCount": 13021, // what the video has, not what this run returned"error": null,"errorType": null,"scrapedAt": "2026-09-09T05:53:05.487Z","source": "youtube.com","sourceUrl": "https://www.youtube.com/watch?v=iG9CE55wbtY"}
With "includeReplies": true, each comment is followed by its replies as rowType: "reply" rows carrying the same columns, with parentCommentId set and a commentId of the form <parent>.<reply>. Sorting by position reproduces the thread exactly as it was read.
Use cases
- Audience research — what people actually say under your videos, or under a competitor's, with the like count as a rough vote on each opinion.
- Sentiment and topic analysis — one flat array of text, ready to hand to a model.
likeCountweights it;publishedAtApproxlets you slice it by period. - Creator and community reporting — filter on
authorIsCreatorto pull the uploader's own replies, or onisHeartedandisPinnedfor what they chose to endorse. - Support and product feedback — the comments under a launch video are a bug tracker nobody filed.
- Moderation and brand safety review — read a section end to end and route it wherever your review process lives.
- Spam and bot-pattern detection — repeated text across channels, or a burst of
newestcomments from accounts with no handle, both show up in this shape.
How it works, and why it keeps working
YouTube's public Data API charges quota for comments, tops out at 100 per page, and needs a Google Cloud project and an API key. This Actor reads the same comment section the browser reads, through the private InnerTube next endpoint the YouTube web player itself calls.
That endpoint answers in a shape worth understanding, because it is the reason cheap scrapers get this wrong:
- Asking
nextfor a video returns no comments — only a continuation token for the comment section. - Posting that token back returns a page of twenty threads, each of which is almost empty: a bag of opaque keys.
- The comment text, author, like count and heart state arrive separately, in a flat
frameworkUpdatesentity batch keyed by those same keys.
So every page has to be stitched: index the entities, resolve each thread against them. Reply pages use a third shape again, and their "next page" token hides behind a "Show more replies" button rather than the usual continuation — miss that one and every long thread silently truncates at ten replies. This Actor reads both spellings.
| Step | Request | What it gets |
|---|---|---|
| 1 | next with the video ID | The video title and the comment-section token — or "comments are turned off" |
| 1b | Watch page (ytInitialData), only if step 1 returns no section | The same token, obtained the slow way; survives some API-level refusals |
| 2 | next with the token | The header (total count, sort menu) and page one, twenty comments plus the next token |
| 3… | next with each next token | Twenty more each time, until your cap, your budget, or the end of the section |
| R | next with a thread's replies token | One thread's replies, when includeReplies is on |
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 comment it already delivered, and files one free blocked row saying where it stopped.
Pages of the same video are spaced 300–700 ms apart. Nothing forces that; it is the difference between reading a comment section 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 video URL.
- Replies are real rows, not a nested blob. One row per reply, with
parentCommentIdandposition, so a spreadsheet or a SQL table can hold the thread without a JSON parser. - The counts are honest about their precision.
likeCountis the integer you want;likeCountTextis the string YouTube actually published, so you can see when "6.1K" is all the precision that exists. - Residential proxy is in the price. YouTube blocks datacenter IPs wholesale, so a comment 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. Comments turned off, a removed video, a bad ID or a block produce a diagnostic row with an
errorTypeyou 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
maxRunSecsor your charge ceiling on page 40 and you keep pages 1–39.
Input reference
| Field | Type | Default | What it does |
|---|---|---|---|
videoUrls (required) | array of strings | prefilled with one video | Watch URLs, youtu.be links, Shorts, embed or live URLs, or bare 11-character video IDs. Duplicates are read, and billed, once |
maxCommentsPerVideo | integer | 100 | Top-level comments per video. YouTube serves twenty at a time, so the walk stops on the page that reaches your number. Replies do not count towards it. 0 = every comment the video has |
sort | top | newest | top | YouTube's own two orders. newest costs one extra request per video — see the FAQ |
includeReplies | boolean | false | Return each thread's replies as their own rows, threaded in behind the comment |
maxRepliesPerComment | integer | 50 | Cap per thread, when replies are on. 0 = the whole thread |
includeAuthorDetails | boolean | true | Fill in authorChannelId, authorUrl and authorAvatarUrl. Off keeps the display name and the badges |
maxConcurrency | integer | 3 | Videos in parallel. Each worker keeps its own proxy session. Pages within one video cannot be parallelised |
maxRunSecs | integer | 240 | Whole-run wall-clock budget. When it runs out the Actor keeps what it has and files a free diagnostic row for each video it never reached |
proxyConfiguration | object | Apify residential | Leave 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 comment or a reply; ok: false is a free diagnostic row.
| Field | What it is |
|---|---|
rowType | comment, reply or diagnostic |
videoId, videoUrl, input, videoTitle | The video, its canonical watch URL, the entry you supplied, and the title |
commentId, parentCommentId | YouTube's IDs. A reply's ID is <parent>.<reply> |
text | The comment body exactly as posted — newlines and emoji kept, nothing trimmed |
authorName | Display name, which YouTube now renders as @handle |
authorChannelId, authorUrl, authorAvatarUrl | The author's identity, when includeAuthorDetails is on |
authorIsCreator, authorIsVerified, authorIsArtist | Uploader, verified badge, Official Artist Channel badge |
likeCount, likeCountText | Likes as an integer, and as YouTube rendered them |
replyCount | How many replies the comment has, whether or not you asked for them |
publishedTimeText, publishedAtApprox, isEdited | When it was posted, both ways, and whether it was edited since |
isPinned, isHearted | Pinned to the top by the uploader; hearted by the uploader |
sortOrder, position | Which order was walked, and where this row fell in it |
totalCommentCount | How many comments the video has in total |
ok, error, errorType | Whether this row is a comment, and if not, why not |
scrapedAt, source, sourceUrl | When, and from where |
errorType on a diagnostic row is one of:
| Value | Meaning | Charged? |
|---|---|---|
comments-disabled | The uploader turned comments off for this video | No |
no-comments | The section exists and is empty | No |
unavailable | Private, removed, region-blocked, or a page with no comment section | No |
blocked | YouTube refused our requests from two different residential exits. Comments already returned for that video are kept | No |
invalid-id | The entry was not a YouTube video — a channel, a playlist, or something else | No |
timeout | The run's maxRunSecs budget ran out before this video was reached | No |
Pricing
$0.35 per 1,000 comments. Pay-per-event, with the residential proxy already inside that number — there is no separate proxy line on your bill for this Actor.
| Event | What triggers it | FREE | Starter | Scale | Business |
|---|---|---|---|---|---|
| Comment returned (primary) | One comment or reply row written to your dataset | $0.00035 | $0.00035 | $0.00028 | $0.00021 |
| Run started | Once per run, after the first comment | $0.001 | $0.001 | $0.001 | $0.001 |
Worked example. 50 videos at 100 comments each, of which 2 videos have comments turned off:
- 48 videos × 100 comments × $0.00035 = $1.68
- 1 run start = $0.001
- 2 videos with comments off = $0.00
- Total: $1.681
What you are never charged for: a video with comments turned off, an empty comment section, a private or deleted video, an entry that was not a video, a video 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
Like counts above 1,000 are approximate. YouTube does not publish an exact like count for a comment anywhere — its own interface shows "6.1K", and that abbreviated string is the entire truth available over any interface, this Actor's included. likeCount expands it to 6100 so the column is sortable; likeCountText keeps "6.1K" so you can see the precision you have. Below 1,000 the number is exact.
Comment timestamps are relative, not absolute. Same reason: YouTube renders "7 years ago" and publishes nothing more precise. publishedAtApprox resolves that 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. publishedTimeText is the raw phrase, and it is the honest one.
Comments you can only see signed in. Held-for-review comments, comments hidden by a moderation filter, and members-only sections need an account. 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.
Reply depth. YouTube's comment tree is two levels deep. A reply to a reply is filed by YouTube itself under the same top-level comment, so parentCommentId always names a top-level comment and there is no third level to miss.
Live streams and premieres. A live chat is not a comment section and this Actor does not read it. A stream that has ended and kept its comments reads normally.
YouTube may change the format. This reads a private endpoint that YouTube changes without notice — that is true of every tool that reads YouTube comments, 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 300–700 ms pause between pages of the same video. Three videos 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-comments-api/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"videoUrls":["iG9CE55wbtY"],"maxCommentsPerVideo":50,"sort":"newest"}'
# pip install apify-clientfrom apify_client import ApifyClientclient = ApifyClient("<APIFY_TOKEN>")run = client.actor("insight.solutions/youtube-comments-api").call(run_input={"videoUrls": ["https://www.youtube.com/watch?v=iG9CE55wbtY"],"maxCommentsPerVideo": 200,"sort": "top","includeReplies": True,"maxRepliesPerComment": 20,})for row in client.dataset(run["defaultDatasetId"]).iterate_items():if not row.get("ok"):print("skipped:", row["input"], row["errorType"])continueindent = " " if row["rowType"] == "reply" else ""print(f'{indent}{row["authorName"]} ({row["likeCount"]} likes): {row["text"][:80]}')
Turn includeAuthorDetails off when you only need the text and the numbers — the payload shrinks and the price is the same.
FAQ
Why are like counts approximate?
Because YouTube's own comment section is. It renders "6.1K" and publishes no exact number behind it, for any client. Anything from 6,050 to 6,149 arrives as 6100, and likeCountText keeps the original string so the loss is visible rather than hidden. Under 1,000 the count is exact.
Why are the dates relative?
Same reason. A comment carries "7 years ago" and nothing else — there is no exact posting time in the response to read. publishedAtApprox turns the phrase into a sortable instant measured back from scrapedAt; treat it as a bucket, not a timestamp.
How many comments can I get from one video?
As many as it has. Set maxCommentsPerVideo: 0 and raise maxRunSecs. A video with 13,000 comments is 650 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 does newest cost an extra request?
Because "newest" is not a parameter — it is a different continuation token, and the only place that token exists is the sort menu in the header of page one. So a newest walk fetches page one under the default order, reads the token, and starts again. One extra request per video, and there is no cheaper way to get it.
Are replies charged?
Yes, at the same rate as comments — a reply is a row. That is why includeReplies is off by default and maxRepliesPerComment exists.
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-comment price. No Google Cloud project, no YouTube Data API key, no quota.
Will it handle a playlist or a whole channel? Not yet — this Actor takes videos. Feed it a list of video URLs from wherever your list comes from.
What happens if one video fails?
The others still run. The failed one produces a free diagnostic row and the run finishes SUCCEEDED. If every video 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.
Legal and data-protection notes
- Public videos only. Every source is a public watch 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, members-only or held-for-review content.
- Comments are personal data in most jurisdictions. A comment carries a display name, a channel ID and an avatar, and under the GDPR and similar laws that is personal data about an identifiable person. You are the controller of whatever you collect: have a lawful basis, keep only what you need, honour deletion requests, and remember that a comment deleted on YouTube stays in your dataset until you remove it.
includeAuthorDetails: falseis there so that analysis which does not need identities does not carry them. - Comments 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, sentiment analysis and quotation are the ordinary uses and are what this is built for.
- Not affiliated with YouTube, Google LLC, or with any channel, creator or commenter 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
- YouTube Transcript API — captions as timed segments, text, SRT or VTT, with language fallback and translation.
- YouTube Channel API — a channel's videos, Shorts and live streams, plus YouTube search.
- Podcast Search, Episodes & Charts API — Apple Podcasts search, charts and full episode feeds.
- Bluesky Scraper — profiles, posts, followers and follows from the public AT Protocol API.
- Telegram Channel Scraper — posts, views and channel stats from public Telegram channels.
- Substack Scraper — posts with full free text, comments and publication profiles.
News, documents & the web
- Google News Search, Topics & Real Article URLs — news search and topic feeds with the publisher's real URL decoded.
- Website to Markdown — Content Extractor for LLMs & RAG — any site as clean Markdown, text and heading-aware chunks.
- Internet Archive API — archive.org search, item metadata, files and reviews.
- Wayback Machine Toolkit — archived URL inventories, snapshots and text diffs between dates.
- Website Technology Detector — the tech stack behind any site, with the evidence for each detection.
- Domain Intelligence API — DNS, RDAP registration, TLS certificate and HTTP facts in one row per domain.
- SEO Page Audit — sitemap crawl with on-page checks, structured data and broken-link reports.
- Keyword Suggestions API — Google, YouTube, Bing, Amazon and eBay autocomplete with alphabet and question expansions.
- Website Contact Extractor — emails, phone numbers and social profiles from any list of websites.
Business, finance & jobs
- Congress & Insider Trades API — STOCK Act periodic transaction reports and SEC Form 4 insider trades in one schema.
- SEC EDGAR API — filings, XBRL financials and full-text search by ticker or CIK.
- Y Combinator Companies, Batches & Founders — the YC directory with founders and social links, filterable by batch, industry and hiring status.
- Career Site Jobs API — jobs straight from Greenhouse, Lever, Ashby, Workable and 10+ other ATS career sites.
- New Job Postings Monitor — new, closed and changed postings on the career sites you watch.
- Shopify Products API — any Shopify store's catalogue, variants, prices and stock signals.
Apps & games
- App Store & Google Play Reviews API — reviews from both stores with ratings, versions and developer replies.
- App Store Top Charts & App Search API — Apple top charts by country and genre, plus app search and details.
- Steam Reviews API — Steam reviews with playtime, helpfulness and game details.
- Steam Game Data API — prices, tags, review scores, live player counts and top charts.