# YouTube Comments Scraper — Replies, Likes & Channel Videos (`steadyfetch/youtube-comments-scraper`) Actor

Paste YouTube video, Shorts or channel links and get the comments as rows: text, author, likes, reply count, pinned and creator-heart flags, plus the replies underneath at the same price. Top or newest order, exact per-video limit, no start fee. Comments off is an uncharged row, never a charge.

- **URL**: https://apify.com/steadyfetch/youtube-comments-scraper.md
- **Developed by:** [Steadyfetch Team](https://apify.com/steadyfetch) (community)
- **Categories:** Social media, Videos, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 1,000 comments

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## YouTube Comments Scraper — Replies, Likes & Channel Videos

Click **Start** with nothing set and the run returns a small **built-in sample**: three rows cut from a public video's own comment page, one of them a reply folded under its parent, so you can read the columns before you plan a big run. It is built in rather than fetched — **nothing is collected and nothing is charged**. The comment text and the video are as they were published; the commenter names in the sample are anonymised, and the rows say so. No login, no cookies, nothing to configure, no start fee and no result fee.

**Using an AI agent?** Pin this actor in Apify's MCP server with one link: `https://mcp.apify.com?tools=steadyfetch/youtube-comments-scraper`

- **Actor id:** `steadyfetch/youtube-comments-scraper`
- **Input:** `{ "videoUrls": ["aqz-KE-bpKQ"] }` — the one field you have to set. A bare 11-character video id as shown, or the watch link, `youtu.be` link, Shorts, live or embed URL you copied. Add `"resultsLimit": 200` for an exact per-video limit, and `"channels": ["@NASA"]` to take a channel's newest uploads instead of naming videos one by one.
- **Cap the bill:** set `maxTotalChargeUsd` on the run (a run option, not Actor input), e.g. `0.50` — the run stops when it reaches it.

*Unofficial. Not affiliated with, endorsed by, or sponsored by YouTube or Google LLC.*

**Paste a channel link and it works.** A handle, `/@handle`, `/channel/UC…`, `/c/…` or `/user/…` returns the comments under that channel's newest uploads, `videosPerChannel` of them (default 5). No other comments scraper on this store takes a channel link at all.

### What one comment costs

One charged event, `comment`, and it lands only on a comment that was actually delivered. **A reply is a comment at the same price, folded under its parent** — never a second event and never a second purchase.

| Apify plan | Per comment | Per 1,000 comments |
|---|---|---|
| Apify free plan | $0.0024 | $2.40 |
| Bronze | $0.0014 | $1.40 |
| Silver | $0.0009 | $0.90 |
| Gold and above | $0.0006 | **$0.60 per 1,000 comments** |

The headline on this listing reads **from $0.60 per 1,000 comments**, and the table above is what that means on your own plan — so you can work out the bill before you start rather than after. Platform usage is included in that price: there is no separate compute bill on top, no start fee, and no charge for a comment that was not delivered. Set `maxTotalChargeUsd` on the run and it is a hard ceiling — the run stops cleanly under it and the last row says what is left.

### What you get

One row per comment. A reply is a row like any other, with `isReply` and `parentCommentId` saying where it belongs, so a thread reads in order.

| Column | What it holds |
|---|---|
| `commentId` | the comment's own stable YouTube id |
| `text` | what they wrote |
| `authorName` · `authorChannelId` · `authorUrl` | who wrote it |
| `authorIsVerified` · `authorIsCreator` · `authorIsArtist` | YouTube's own badges on that author, including whether it is the uploader replying |
| `likeCount` · `likeCountText` · `likeCountIsApproximate` | the parsed number, the label YouTube actually published, and whether that label was rounded |
| `replyCount` | how many replies the comment has |
| `publishedText` · `publishedAt` · `publishedAtPrecision` | the label YouTube published, the date derived from it, and how precise that derivation really is |
| `isPinned` · `heartedByCreator` | pinned by the uploader, and hearted by them |
| `isReply` · `parentCommentId` | whether it is a reply, and to which comment |
| `videoId` · `videoUrl` · `videoTitle` · `channelName` · `channelId` | the video it came from and who uploaded it |
| `sort` · `source` | which order this row was collected in, and whether the video came from your list or from a channel |
| `charged` · `chargedEvents` · `status` · `statusReason` | whether this row was billed, which event billed it, and why — so a single row can be checked against your invoice, and the whole dataset reconciles it |

**Your limit is exact and it is per video.** Set `resultsLimit` to 200 and you get 200 from each link, replies counted inside that number. If your input carries `maxComments`, `commentsPerVideo`, `limit` or `count` instead — the names other comment scrapers use — it is read as `resultsLimit` and one uncharged note row says so. `maxItems` (default 1,000) is the ceiling on the whole run.

### How deep this goes

This is the one thing to know before you ask for a big number, and it is the question this listing exists to answer honestly. There are two separate limits on it and they are worth keeping apart: **how deep YouTube will serve**, which is the same for everyone, and **how deep one run of yours goes**, which is set by your own settings.

#### What YouTube serves

**YouTube itself stops serving comment pages**, long before a big video runs out of comments, and how far it goes depends on the order you ask for. Walked to exhaustion twice, a day apart, on 2026-09-11 and 2026-09-12:

| Order | Where YouTube stopped serving |
|---|---|
| `sort: top` (the default) | about **1,200 comments per video, whatever the video's size** — around 1,220 on a video with 2.46 million comments, and around 1,130–1,140 on one with 5,171 |
| `sort: newest` | still serving at **3,000 comments** when we stopped walking, with more to come — deeper than Top, and we have not found the end of it |

That ceiling is YouTube's, not ours, and no setting on this actor moves it. Every walk ended cleanly — YouTube simply stopped handing out the next page — rather than being blocked.

**And it is approximate, which is why this table says "around".** We walked the same video on the same order a day apart and YouTube stopped at 1,130 comments one day and 1,137 the next, both times on a clean end. It is YouTube deciding when to stop serving, not a published limit, so the number moves a little between runs. Plan against the range, not against a figure — and when your own run lands a few comments either side of it, that is the surface, not a fault in the run.

So: if you want depth, ask for `sort: newest`. If you want the comments people actually engaged with, `sort: top` is the right order and around 1,200 is what exists. When the pages run out before your `resultsLimit` is reached, the run leaves an **uncharged** `depth_reached` row saying how many comments that order served for that video and pointing at `sort: newest` as the deeper door. You are never billed for the gap, and you are never left to work out that there was one.

**Two of our own bounds end a walk early too, and the row says so instead of blaming YouTube.** One video will not walk past 400 comment pages, so it can never spend a whole run on its own; and a re-run whose pages hold only comments your account already has stops after ten such pages rather than keep buying pages that add nothing (a token that cannot open key-value stores cannot see what you already have — the run says so and collects as usual). Both end in the same uncharged `depth_reached` row, and in both cases the row names the bound as ours and the setting that opens it — **Include comments you already have** for the second, the other comment order for either.

#### What a default run returns

**Those are ceilings on what you CAN reach, not on what a default run brings back — your own settings stop it first, and on purpose.** A run that you start without changing anything ends at whichever of these three comes first, and they are listed in the order they usually bind:

- **`resultsLimit`, which defaults to 100.** This one is per video, and it is what stops an ordinary run: paste one link, press Start, and you get 100 comments from it — replies counted inside that number — not the thousand-odd the table above says are there.
- **`maxItems`, which defaults to 1,000.** A ceiling on the whole run, every video and every reply counted together. This is the one that binds when you paste a long list of links or point the run at a channel.
- **`maxRunSeconds`, which defaults to 600.** Collecting takes time, and a deep walk into a busy video can reach the clock before it reaches the rows.

Whichever one stops it says so on an **uncharged** row naming the setting, so you never have to guess which of your own limits bound the run. To go deeper on one video, raise `resultsLimit`; to collect from more videos in a run, raise `maxItems` too; raise `maxRunSeconds` only if the clock is what the row actually named. Keep `maxTotalChargeUsd` on the run as your ceiling on the bill — and note what the depth you are asking for costs: a thousand comments is $2.40 on the Apify free plan and $0.60 on Gold, so the defaults are deliberately modest rather than generous. The numbers in the table above are what is there to be collected once you have raised them.

### Dates and likes

**YouTube publishes relative labels, not timestamps.** What the page carries is "1 year ago" or "16 minutes ago" — there is no exact date on the surface at all, for anyone. So the row ships three columns instead of one invented one:

- `publishedText` — the label, verbatim, exactly as YouTube published it.
- `publishedAt` — a date derived from that label.
- `publishedAtPrecision` — `minute`, `hour`, `day`, `week`, `month` or `year`: how precise that derivation actually is.

A comment labelled "1 year ago" comes back with `publishedAtPrecision: "year"`, never a millisecond stamp dressed up as a fact. Sort by `publishedAt` and it behaves; read it as an exact time and the precision column tells you not to.

**Likes above 999 are rounded by YouTube** — the page says "314K", not 314,271. The row carries `likeCount` (314000), `likeCountText` (`"314K"`) and `likeCountIsApproximate` (`true`), so the rounding is named rather than hidden inside a number that looks exact.

### When a video has no comments to give

Every one of these is still **delivered as a row**, labelled, with a sentence saying which it was — never a silent gap in your dataset, and none of them is charged.

| the row says | what happened |
|---|---|
| `comments_disabled` | the uploader turned comments off for that video. We learn it from the video's own page, before any comment page is requested, so it costs one read and it costs you nothing |
| `no_comments` | comments are on and nobody has left one. A definitive answer, not a failure, and never retried |
| `removed_or_unavailable` | the video is gone |
| `private_or_members_only` | the video is private, or behind a membership |
| `age_restricted` · `region_blocked` | the video is gated to signed-in or to other regions |
| `channel_not_found` · `not_a_channel` · `channel_no_videos` | the channel link did not resolve, or the channel has no uploads to read |
| `depth_reached` | the walk ended before your limit — YouTube stopped serving pages, or one of our two bounds did. The row says which, and what to change |
| `skipped_budget` · `skipped_deadline` | your own `maxTotalChargeUsd` or the run clock ended the collecting |
| `comments_unavailable` | the read did not go through. Temporary, says nothing about your video, and a re-run is the fix |
| `input_error` — and `input_error.videoUrls`, `input_error.channels`, `input_error.startUrls`, `input_error.sort` | something in that field could not be read. The row names the field, never what you typed |

**"Comments are turned off" and "this video is gone" are two different answers, and this actor tells them apart.** That distinction costs one extra read on our side and nothing at all on yours — it matters because a run that reports "not found" about a video you can watch is a bug report you would have to file.

### How often this data changes

Comments keep arriving for days after a video goes up — the first hours are the busiest — and likes on a comment keep moving for weeks after the comment itself stops changing. A channel's newest uploads move every time it publishes. So the useful pattern here is not one run: it is the same links again tomorrow.

Re-running would otherwise re-buy the comments you already have, so it does not. Every run remembers what it delivered to your account, in the key-value store `yt-comments-account` in your own Apify account, and a later run skips those: not delivered again, not charged, and the slot goes to the next new comment instead — so you still get the number you asked for, as long as there are that many new ones to find. When a re-run meets ten pages running with nothing new on them, it stops there and says so on an uncharged row rather than buying deeper into comments you already own. That makes a daily or weekly watch cost only what is new. The memory is a key-value store in your own Apify account, so a run started with a scoped API token in restricted-access mode needs key-value store Read, Write and Create permission, or Actor runs set to Full access (Write is what lets the run remember what it delivered; without it every run pays again); without it the run says so on its status line and on an uncharged row of its own, and cannot skip what you already have. Switch **Include comments you already have** (`includeSeen`) on to get them back anyway, marked and still uncharged, and use `resumeFromDatasetId` to carry the same memory across accounts.

For a video older than a few weeks on `sort: top`, a second run buys almost nothing new — that one is worth reading once. For an upload from this week, or for a channel you are tracking, it is worth a schedule.

### Limits, and the run's own manners

**Your settings are never refused for being too big.** Ask for more comments per video than YouTube will serve, more videos from a channel than the run can walk, or a shorter clock than the work needs, and the run still starts: it continues at the nearest limit and leaves one uncharged row saying what you asked for and what bound it. `maxRunSeconds` (default 600) is the clock, `videosPerChannel` (default 5) the channel door's depth, `sort` is `top` or `newest` and an order we do not know is refused on an uncharged row naming the two that work — never quietly swapped for something else.

**A cost cap or a time limit ends the collecting, never the delivering.** Comments already in hand are written out whatever stops the run, and the last row of every run is a receipt: delivered, how many were replies, asked for, what your account already had, what stopped it, and the charged totals — so an invoice reconciles from the dataset itself.

Only public videos are read. This actor may stop working if YouTube changes how comments are served; if it does, the rows say so and nothing that failed is charged.

### Related actors

- The spoken words inside one video: [YouTube Transcript API & Scraper — YouTube Subtitles, Speech AI](https://apify.com/steadyfetch/youtube-transcript-scraper)
- Every video on a channel, as text: [YouTube Channel Videos Transcript Scraper — Bulk Video to Text](https://apify.com/steadyfetch/youtube-channel-transcripts)

### Something not right?

Open the **Issues** tab on this actor with your run ID and the input you used, and it gets looked at — usually within a couple of hours.

If it earned its keep, a rating helps other buyers find it, and saving the actor keeps it one click away.

# Actor input Schema

## `videoUrls` (type: `array`):

YouTube video links or ids, one per line: https://www.youtube.com/watch?v=<id>, youtu.be/<id> (a ?si= tracking tail is fine), /shorts/<id>, /live/<id>, /embed/<id>, /v/<id>, or the bare 11-character id. Each one is read once for its comment page and then walked under the order and the per-video limit set below. A playlist link is refused on an uncharged row naming the field, because a playlist is not a video. Leave this empty and the run returns a small built-in sample from one public video instead of collecting anything, at no result fee.

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

YouTube channels, one per line — @handle, https://www.youtube.com/@handle, /channel/UC…, /c/… or /user/… . The newest uploads of each channel ("Videos per channel" below) are taken in turn and their comments delivered with the same columns, so you never have to paste video links yourself. A channel that does not exist, or a link that is not a channel, ships one uncharged row saying so; a channel that lists no public videos ships one too.

## `startUrls` (type: `array`):

One list for every kind of YouTube link, for buyers moving over from another scraper: video links, Shorts links and channel links together. Each line is sorted into the right door by its own shape before anything is read, so a channel link never gets asked for as a video and a video link never triggers a channel listing. A playlist or a community-post link is refused on an uncharged row naming this field.

## `sort` (type: `string`):

top (default) is YouTube's own "Top comments" order — the pinned comment first, then by engagement. newest is "Newest first". They are different feeds: Top stops serving pages at roughly 1,200 comments on the videos we measured, while Newest kept going past 3,000, so newest is the order to ask for when you want depth. Anything other than top or newest is refused, uncharged, naming both values.

## `includeReplies` (type: `boolean`):

ON (default): the replies under each comment are delivered too, each as its own row with isReply: true and the parentCommentId it answers, so a thread can be rebuilt exactly. A reply is charged as a comment — the same event and the same price, never a second or a separate purchase — and replies count against "Max comments per video" like any other row. OFF: top-level comments only.

## `resultsLimit` (type: `integer`):

How many comment rows to take from EACH video — exact, never overshot and never padded, and replies count toward it. Delivered equals charged: a video that holds fewer gives you what it holds and the run says so. ⚠ YouTube itself ends the comment feed at some depth — measured at about 1,200 on top order and beyond 3,000 on newest — and a run that reaches that end ships one uncharged row naming the count it stopped at and the order that goes deeper. Other scrapers call this maxComments (the biggest listing's own name for it, where it defaults to 1), commentsPerVideo, limit or count; every one of those names is read as THIS field — the per-video one — and the smallest of them wins, with one uncharged note row saying which was read. Accepted range 1 to 5,000; a number outside it is moved to the nearest value in range and one uncharged note row says so, rather than the run being refused before it starts.

## `maxItems` (type: `integer`):

Hard ceiling for the WHOLE run, counting comments and replies together across every video and every channel, whatever else is set. The per-video cap is "Max comments per video" above. ⚠ maxComments, commentsPerVideo, limit and count are NOT read as this field: on the listings that use those names they mean the PER-VIDEO limit, so they are folded onto "Max comments per video" above and a note row says so. This box is the run-wide ceiling and has to be set by its own name. Accepted range 1 to 100,000; a number outside it is moved to the nearest value in range.

## `videosPerChannel` (type: `integer`):

How many of each channel's NEWEST uploads to take comments from. Read only by the "Channels" box and by channel links in "Start URLs"; a video you named yourself ignores it. Each of those videos then gets its own "Max comments per video" share, so 5 videos at 100 comments is an ask of 500 rows. Accepted range 1 to 50; a number outside it is moved to the nearest value in range and one uncharged note row says so.

## `maxRunSeconds` (type: `integer`):

The run stops cleanly before this many seconds and reports what is left, instead of being killed by a timeout. A time limit ends the collecting, never the delivering: rows already in hand are always written out. Accepted range 30 to 3,600 seconds; a number outside it is moved to the nearest value in range and one uncharged note row says so.

## `includeSeen` (type: `boolean`):

OFF (default): a comment already delivered to your account is skipped — no row, no charge. ON: those comments are delivered again anyway, marked repeat: true, and still not charged. Anything older than 90 days in your account's memory counts as new again either way.

## `resumeFromDatasetId` (type: `string`):

The dataset ID of a previous run of this actor. Comments already in it are treated as rows you already have and are skipped, so a follow-up run collects only what is new even from a different Apify account. Selecting it here is what grants this run permission to read it.

## Actor input object example

```json
{
  "videoUrls": [],
  "channels": [],
  "startUrls": [],
  "sort": "top",
  "includeReplies": true,
  "resultsLimit": 100,
  "maxItems": 1000,
  "videosPerChannel": 5,
  "maxRunSeconds": 600,
  "includeSeen": false
}
```

# Actor output Schema

## `comments` (type: `string`):

One row per comment delivered, with the same columns whichever door it came through — a video link you pasted or a channel whose newest uploads were taken: the stable YouTube comment ID and the video it belongs to (ID, URL and title) with that video's channel name and ID, the comment text, the author's display name, channel ID and channel link with the verified, channel-owner and official-artist flags YouTube marks them with, the like count as a number, the like text exactly as YouTube shows it and a flag saying whether that number is one of YouTube's rounded ones, the reply count as a number, the reply text exactly as YouTube shows it and its own flag saying whether that number is one of YouTube's rounded ones too — YouTube abbreviates a reply count ("1K") exactly as it abbreviates likes, and neither flag has anything to do with what the row costs, the published label as published and a timestamp derived from it with the precision it was derived at, whether the comment is pinned and whether the creator hearted it, whether it is a reply and the comment it answers, the order it was collected under and the door it came through. ONE EVENT, ONE FLAG: every delivered comment row is charged as exactly one `comment` — `charged: true` says this row was billed and `chargedEvents` names it. A REPLY IS A COMMENT: it lands as its own row with `isReply: true` and `parentCommentId` set, at the same price as any other comment, and is never a second or a separate purchase. There is no start fee and no per-video lookup fee: reading the video page and listing a channel's uploads is our cost, not a charged event.

## `misses` (type: `string`):

One uncharged row for every video, channel or link that returned less than it was asked for, saying which happened: `comments_disabled` (YouTube answered that comments are turned off for this video — the answer comes from the one page read before any comment page is fetched, so nothing was collected and nothing is owed), `no_data` (the video is real, comments are on, and there are none), `depth_reached` (YouTube's own comment feed ended before your per-video limit — the row names the count it stopped at and says that `sort: newest` reaches further than `top`; the comments delivered up to that point were charged, the rest were never collected and are not billed), `removed_or_unavailable`, `private_or_members_only`, `age_restricted` and `region_blocked` (YouTube's own definitive verdicts about the video — a re-run changes none of them), `source_unavailable` (the comment feed would not answer after its retries — ours, and a re-run is the fix), `blocked_retry` (a bot or consent wall after the identity ladder — also retryable), `channel_not_found` (the channel does not exist, or the link is not a channel), `empty` (the channel listed no public videos), `skipped_max_items`, `skipped_budget` and `skipped_deadline` (your own row ceiling, your own maxTotalChargeUsd, or the run clock stopped it first), `skipped_seen` and `skipped_duplicate` (your account already had that comment, or two of your own targets reached it) and `input_error` with the field it names. None of these is charged, and `charged: false` on the row is what says so.

## `summary` (type: `string`):

Rows delivered by door — videos you named against channels that were listed — top-level comments against replies, what was asked for, comments your account already had, what stopped the run, and the charged-event total for `comment`, which equals the number of rows above it saying `charged: true`.

# API

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

## JavaScript example

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

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

// Prepare Actor input
const input = {
    "videoUrls": [],
    "channels": [],
    "startUrls": []
};

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

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

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

```

## Python example

```python
from apify_client import ApifyClient

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

# Prepare the Actor input
run_input = {
    "videoUrls": [],
    "channels": [],
    "startUrls": [],
}

# Run the Actor and wait for it to finish
run = client.actor("steadyfetch/youtube-comments-scraper").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "videoUrls": [],
  "channels": [],
  "startUrls": []
}' |
apify call steadyfetch/youtube-comments-scraper --silent --output-dataset

```

## MCP server setup

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

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/7olXUyrIoics0gBH6/builds/EXgodDWhTyMomixiy/openapi.json
