# YouTube Scraper - Most Comprehensive (`kaix/youtube-scraper`) Actor

🔥 ~$0.05/1K contents 🔥 Get public YouTube videos, channels, playlists, podcasts, courses, Posts, clips, comments, captions, and live chat.

- **URL**: https://apify.com/kaix/youtube-scraper.md
- **Developed by:** [Kai](https://apify.com/kaix) (community)
- **Stats:** 1 total users, 1 monthly users, 88.2% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.04 / 1,000 contents

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## YouTube Scraper

Collect YouTube videos, Shorts, streams, channels, playlists, podcasts, courses,
Posts, clips, comments, chat, and captions as structured data. Search by topic,
read a channel tab, or fetch known content by URL. Results go to the default
dataset. Public collection needs no YouTube account or YouTube API key.

### Use cases

- Content research: find videos by topic, publication period, duration, and available features.
- Channel analysis: compare public channel data, uploads, Shorts, streams, and collections.
- Caption datasets: collect text, timed segments, available languages, and subtitle files.
- Discussion research: collect comments, replies, creator hearts, and public channel Posts.
- Podcast and course catalogues: find collections and collect their ordered video entries.
- Live event research: collect available stream data and a bounded chat snapshot or replay.
- Recommendation analysis: record related videos and their order for a source video.

### Video, channel, and discussion data

- Videos: identity, title, description, channel, publication date, duration, views, likes, comments, tags, hashtags, category, and thumbnails.
- Video details: chapters, music credits, description links, location, collaborators, heat maps, storyboards, end screens, caption languages, and playback format metadata.
- Streams: scheduled, live, or ended state, scheduled start time, current viewer count, and latency flags when YouTube supplies them.
- Channels: descriptions, subscriber and video counts, total views, country, join date, images, external link destinations, and available tabs.
- Playlists, podcasts, and courses: collection identity, title, author, description, counts, cover images, preview videos, and ordered entries.
- Posts: text, images and galleries, attached videos, polls, quizzes, author data, publication display, likes, and comment counts.
- Comments: text, author, likes, replies, pinned state, creator hearts, artist and edited marks, badges, links, and text attachments.
- Chat: text, paid messages, stickers, memberships, gifts, notices, message updates and removals, other source events, author badges, timestamps, replay offsets, and original event data.
- Clips: title, source video, start and end times, duration, and available creator data.
- Captions: YouTube transcripts, timed segments, word offsets, available translations, and subtitle files.

Fields depend on what YouTube exposes for the target. Missing exact values
remain `null`. Display labels, such as rounded counts and relative dates, stay
separate from exact values.

### Choose how to collect

| What you need | Set `mode` to | Required input |
|---|---|---|
| Mixed YouTube targets, or automatic query handling | `auto` | `targets` or `queries` |
| Results for a topic | `search` | `queries`; use `search.type` for channels or playlists |
| Full data for known videos | `video` | Video IDs or URLs in `targets` |
| Channel metadata | `channel` | Channel IDs, handles, or URLs in `targets` |
| A channel's regular videos | `channelVideos` | Channel targets |
| A channel's Shorts | `channelShorts` | Channel targets |
| A channel's current, past, or scheduled streams | `channelStreams` | Channel targets |
| A channel's playlists | `channelPlaylists` | Channel targets |
| A channel's podcasts | `channelPodcasts` | Channel targets |
| A channel's courses | `channelCourses` | Channel targets |
| A channel's public Posts | `channelPosts` | Channel targets |
| Known Posts | `post` | Post IDs or URLs in `targets` |
| Known clips | `clip` | Clip IDs or URLs in `targets` |
| Related videos | `related` | Source video IDs or URLs in `targets` |
| Live chat or available replay | `liveChat` | Video IDs or URLs in `targets` |
| Videos for a hashtag | `hashtag` | Tags, `#tags`, or hashtag URLs in `targets` |
| Ordered playlist entries | `playlist` | Playlist IDs or URLs in `targets` |
| Playlist metadata without its entries | `playlistDetails` | Playlist IDs or URLs in `targets` |
| Video comments and optional replies | `comments` | Video IDs or URLs in `targets` |
| Standalone transcript rows | `transcript` | Video IDs or URLs in `targets` |

The default is `auto`. A base channel target collects regular videos by
default. Set `limits` to include Shorts or streams. Use `channel` mode when
you need only channel metadata.

Video URLs can use watch, Shorts, live, embed, or `youtu.be` paths. Channel
targets accept `@handles`, channel IDs, and channel URLs, including `/c/` and
`/user/` URLs that YouTube can resolve. In auto mode, channel suffixes select
the Videos, Shorts, Streams, Playlists, Podcasts, Courses, or Posts tab.
`/community` selects Posts. Post and clip URLs select their own modes. For a
bare clip ID, select `clip` mode explicitly.

A watch URL with a playlist parameter selects the video in auto mode. Use
`playlist` or `playlistDetails` to select the playlist. Targets must use
supported HTTPS YouTube URLs or IDs for the chosen mode.

### Choose the data depth

| Setting | Use it when | Result |
|---|---|---|
| `includeDetails: false` | You need the fields shown in a result list. | Video, channel, and collection cards with available titles, counts, images, and source context. |
| `includeDetails: true` | You need more data for each result. | Full video, channel, or playlist metadata. Video results can also include full channel fields. |

Auto mode enables details by default. Explicit list modes leave details off
by default. Direct video targets always request full video data. `channel`
and `playlistDetails` already return metadata and do not accept this option.

Captions are a separate option. Set `fields` to keep selected output fields.
Selecting fewer fields changes the saved data; it does not disable captions.

### Collect data

In Apify Console, open Input, paste an example, and start the run. Open the
default dataset for the results. The `SUMMARY` record in the default key-value
store reports saved rows and source outcomes.

Search for recent videos with captions:

```json
{
  "mode": "search",
  "queries": ["machine learning tutorial"],
  "maxItems": 20,
  "search": { "publishedWithin": "month", "features": ["subtitles"] },
  "includeDetails": true
}
```

Collect a channel's regular videos and Shorts:

```json
{
  "targets": ["https://www.youtube.com/@GoogleDevelopers"],
  "limits": { "videos": 10, "shorts": 10, "streams": 0 },
  "maxItems": 20
}
```

Find podcast collections. Use `channelPlaylists` or `channelCourses` for the
other collection tabs:

```json
{
  "mode": "channelPodcasts",
  "targets": ["@lexfridman"],
  "maxItems": 20,
  "includeDetails": true
}
```

Collect the video entries of a known playlist or course:

```json
{
  "mode": "playlist",
  "targets": ["PLOU2XLYxmsILZnKn6Erxdyhxmc3fxyitP"],
  "maxItems": 20,
  "includeDetails": true
}
```

Read public channel Posts:

```json
{
  "mode": "channelPosts",
  "targets": ["@veritasium"],
  "maxItems": 20
}
```

Collect comments and up to five replies per parent:

```json
{
  "mode": "comments",
  "targets": ["dQw4w9WgXcQ"],
  "maxItems": 100,
  "comments": { "sort": "newest", "includeReplies": true, "maxReplies": 5 }
}
```

Save native captions in several languages as VTT files:

```json
{
  "mode": "transcript",
  "targets": ["dQw4w9WgXcQ"],
  "maxItems": 10,
  "captions": { "language": "all", "maxTracks": 10, "format": "vtt", "saveFile": true }
}
```

Collect one available live-chat snapshot:

```json
{
  "mode": "liveChat",
  "targets": ["https://www.youtube.com/watch?v=tj4knR4r1UU"],
  "maxItems": 100,
  "liveChat": { "sort": "all", "maxPages": 5, "maxWaitSeconds": 0 }
}
```

Chat availability can change. Use a video that currently exposes public chat
or replay to the Actor.

<details>
<summary>More examples: channel search, playlists, related videos, and clips</summary>

Find channels for a topic. Set the type to `playlist` to find playlists:

```json
{
  "mode": "search",
  "queries": ["science"],
  "search": { "type": "channel" },
  "maxItems": 20,
  "includeDetails": true
}
```

Read related-video cards without full details:

```json
{
  "mode": "related",
  "targets": ["dQw4w9WgXcQ"],
  "maxItems": 20
}
```

Read a public clip:

```json
{
  "mode": "clip",
  "targets": ["https://www.youtube.com/clip/UgkxU2HSeGL_NvmDJ-nQJrlLwllwMDBdGZFs"]
}
```

</details>

### Input

Supply `targets` or `queries`, not both. Search mode uses `queries`. Other
explicit modes use `targets`. Each list accepts up to 50 strings of at most
2,000 characters. Unknown fields and unsupported option combinations fail
validation before collection.

| Field | Type | Default | Behaviour |
|---|---|---|---|
| `mode` | enum | `auto` | Selects one of the modes above. |
| `targets` | string\[] | `[]` | URLs, IDs, handles, or hashtags for the selected mode. Equivalent targets are collected once. |
| `queries` | string\[] | `[]` | Search terms or YouTube results URLs. Valid in auto and search modes. |
| `maxItems` | integer | `100` | Global dataset row limit from 1 to 1,000. Zero is invalid. |
| `region` | string | `US` | Two-letter uppercase country code for YouTube results. Select the proxy country separately. |
| `limits` | object | See collection behaviour | Per-source `videos`, `shorts`, and `streams` limits, each from 0 to 1,000. |
| `includeDetails` | boolean | True in auto; false in explicit list modes | Adds full details to discovered videos, channels, playlists, and playlist entries. |
| `fields` | string\[] | All available fields | Selects 1 to 100 top-level field names. Identity, status, language, format, and file references remain. |
| `search` | object | No filters | Search type, order, publication period, duration, and features. |
| `channel` | object | Newest order | Channel video-tab order and publication boundary. |
| `comments` | object | Top order; no replies | Comment order and reply limits. Valid only in comments mode. |
| `liveChat` | object | All chat; five pages; no wait | Chat order and collection bounds. Valid only in liveChat mode. |
| `captions` | object | Off, except in transcript mode | Caption source, language, format, files, and word offsets. Supplying this group enables native captions by default. |
| `proxyConfiguration` | object | `{"useApifyProxy":true}` | Apify Proxy or custom proxy settings for YouTube requests. |

#### Search and channel options

| Option | Default | Values and behaviour |
|---|---|---|
| `search.type` | Video collection | `video`, `movie`, `channel`, or `playlist`. Shorts and stream selection take priority for video results. |
| `search.sort` | Server order | `relevance` or `views`. |
| `search.publishedWithin` | No filter | `today`, `week`, `month`, or `year`. |
| `search.duration` | No filter | `short`: less than 3 minutes; `medium`: 3 to 20 minutes inclusive; `long`: more than 20 minutes. |
| `search.features` | `[]` | `hd`, `subtitles`, `creativeCommons`, `3d`, `live`, `purchased`, `4k`, `360`, `location`, `hdr`, `vr180`. |
| `channel.sort` | `newest` | `newest`, `popular`, or `oldest`. The requested order must be available for that channel tab. |
| `channel.publishedAfter` | No filter | Inclusive ISO date or relative age, such as `2026-09-01` or `7 days`. Uses newest channel order. |

`search` applies to auto, search, and hashtag modes. `channel` applies to auto
and the Videos, Shorts, and Streams tab modes. Search controls do not filter
channel tabs. Auto runs need a target to which the supplied controls apply.

Channel and playlist search accept only `search.type` in this group. Use
`maxItems` without video limits, captions, or other search filters. The
same restriction applies to results URLs. Hashtags collect videos and cannot
use channel or playlist result types.

Results URLs retain supported filters unless an explicit control replaces
them. A Shorts or live search URL selects that content when type limits are
omitted. The Actor also checks duration bounds. A result without an exact
duration cannot pass a duration filter.

Hour search is not supported. Inputs with `search.publishedWithin: "hour"`
and results URLs with an hour filter are rejected. Use `today`, `week`,
`month`, or `year` for search.

A channel publication boundary can make collection slower. Relative channel
ages, such as `2 hours` or `7 days`, remain supported and are measured from
the start of the run. That date stays the same if the run resumes.

#### Comment and chat options

| Option | Default | Values and behaviour |
|---|---|---|
| `comments.sort` | `top` | `top` or `newest`. |
| `comments.includeReplies` | `false` | Collects replies as separate rows. |
| `comments.maxReplies` | `10` | From 0 to 100 per parent. Requires `includeReplies: true`. |
| `liveChat.sort` | `all` | `top` or `all`. |
| `liveChat.maxPages` | `5` | From 1 to 25 pages of chat events in the selected order. |
| `liveChat.maxWaitSeconds` | `0` | From 0 to 60 seconds to wait for chat data. Zero collects the currently available snapshot. |

With no wait, current chat returns one snapshot. Replay can collect more
pages if they are available without a wait. A positive value allows the Actor
to wait for more chat data. Collection stops at the row, page, or wait limit.

#### Caption options

| Option | Default | Values and behaviour |
|---|---|---|
| `captions.mode` | `native` when supplied or in transcript mode; otherwise `off` | `off` or `native`. |
| `captions.language` | `en` | A language code, `any` for one available language, or `all` for native tracks across languages. An exact language is preferred over a match in the same language family. |
| `captions.maxTracks` | `10` | From 1 to 20. Valid only with `language: "all"`. Collects one preferred track per language. |
| `captions.translateTo` | None | Output language. Translation requires a translatable track and a language offered by YouTube. `any` and `all` are invalid. |
| `captions.format` | `srt` | `srt`, `vtt`, `xml`, or `plaintext`. |
| `captions.saveFile` | `false` | Saves formatted native captions in the key-value store. |
| `captions.prefer` | `manual` | `manual` or `automatic`. Uses the other track type if the preferred type is absent. |
| `captions.wordTimings` | `false` | Includes available native word offsets relative to each segment start. |

Native mode gets manual or automatic caption tracks from YouTube.

Language `all` requires native mode without translation. Each track is a row
in transcript mode, or an object in `subtitles` on a video or playlist entry.
A failed track does not remove successful tracks. Off mode cannot have other
caption options and cannot be used in transcript mode.

Caption options apply to video-producing modes and transcript mode.
They do not apply to channel metadata, collection discovery, Posts, clips,
comments, or chat. In auto mode, they apply to video and playlist-entry rows.

#### Connection and output options

Proxy access is enabled by default. Set `proxyConfiguration.useApifyProxy` to
false for direct access. The group also accepts `apifyProxyGroups`, a
two-letter `apifyProxyCountry`, or custom `proxyUrls`.

`fields` selects top-level output names, without nested paths. It does not
create absent fields. Saved caption references remain even when full caption
data is omitted. To reduce collection time, disable optional detail or
caption work. Selecting fewer fields only reduces the saved row content.

<details>
<summary>Input examples with advanced options</summary>

Search with video filters, native captions, and selected output fields:

```json
{
  "mode": "search",
  "queries": ["machine learning"],
  "maxItems": 20,
  "region": "SE",
  "limits": { "videos": 20, "shorts": 0, "streams": 0 },
  "includeDetails": true,
  "search": {
    "type": "video",
    "sort": "views",
    "publishedWithin": "month",
    "duration": "medium",
    "features": ["hd", "subtitles"]
  },
  "captions": {
    "mode": "native",
    "language": "any",
    "format": "vtt",
    "saveFile": true,
    "prefer": "manual",
    "wordTimings": true
  },
  "fields": ["title", "url", "viewCount", "channel", "subtitles"],
  "proxyConfiguration": { "useApifyProxy": true }
}
```

Collect channel videos published within the past month:

```json
{
  "mode": "channelVideos",
  "targets": ["@GoogleDevelopers"],
  "maxItems": 50,
  "channel": { "sort": "newest", "publishedAfter": "30 days" }
}
```

Request a native caption translation when YouTube offers it:

```json
{
  "mode": "transcript",
  "targets": ["dQw4w9WgXcQ"],
  "captions": { "language": "en", "translateTo": "es", "format": "srt", "saveFile": true }
}
```

</details>

### Output

The default dataset contains one object per saved item. Each object has a
`type`, `recordId`, input `source`, collection time `scrapedAt`,
`schemaVersion: 1`, and `partial` status. Export the dataset as JSON or CSV.
JSON keeps the nested arrays and objects.

#### Output at a glance

| Type | Main fields |
|---|---|
| `video` | `videoId`, `url`, `title`, `contentType`, `detailLevel`, `description`, `durationSeconds`, `publishedAt`, counts, channel data, and optional details |
| `channel` | `channelId`, `url`, `name`, `description`, `subscriberCount`, `videoCount`, `viewCount`, `country`, `joinedAt`, images, `links`, and `tabs` |
| `playlist` | `playlistId`, `url`, `title`, `description`, `authorName`, `videoCount`, `collectionType`, cover images, and source channel where available |
| `playlist-entry` | `playlistId`, `position`, `videoId`, `url`, `title`, video fields, and nested `playlist` metadata |
| `post` | `postId`, `channelId`, `url`, `text`, `textRuns`, author fields, counts, `images`, `video`, `poll`, and `quiz` |
| `comment` | `videoId`, `commentId`, `parentCommentId`, `text`, author fields, counts, badges, creator marks, links, and attachments |
| `live-chat-message` | `videoId`, nullable `messageId`, `eventType`, `sourceEventType`, `rawEvent`, `text`, author fields, badges, timestamps, replay offset, and payment or membership display |
| `clip` | `clipId`, `videoId`, `sourceVideoUrl`, `title`, `startMs`, `endMs`, `durationMs`, and creator fields |
| `transcript` | `videoId`, `language`, `sourceLanguage`, generation flags, `fullText`, `segments`, `format`, `formattedText`, and saved file references |

Shorts and streams use type `video`. Their `contentType` is `shorts` or
`stream`. Podcasts and courses use type `playlist` with the corresponding
`collectionType`. Metadata discovery and playlist entry collection are
separate modes.

Example from a recorded clip result, with selected fields:

```json
{
  "type": "clip",
  "recordId": "clip:UgkxU2HSeGL_NvmDJ-nQJrlLwllwMDBdGZFs",
  "clipId": "UgkxU2HSeGL_NvmDJ-nQJrlLwllwMDBdGZFs",
  "videoId": "NiXD4xVJM5Y",
  "title": "Test Clip",
  "sourceVideoUrl": "https://www.youtube.com/watch?v=NiXD4xVJM5Y",
  "startMs": 0,
  "endMs": 15000,
  "durationMs": 15000,
  "creatorName": "Futium",
  "creatorChannelId": null,
  "source": "https://www.youtube.com/clip/UgkxU2HSeGL_NvmDJ-nQJrlLwllwMDBdGZFs",
  "scrapedAt": "2026-09-23T11:24:21.612Z",
  "schemaVersion": 1,
  "partial": false
}
```

#### Video and channel fields

<details>
<summary>View video and channel field groups</summary>

| Group | Fields |
|---|---|
| Counts and time | `viewCount`, `viewCountText`, `likeCount`, `likeCountText`, `commentCount`, `commentCountText`, `publishedAt`, `uploadDate`, `publishedTimeText`, `durationSeconds` |
| Channel context | `channelId`, `channelName`, `channelUrl`, `channelUsername`, `numberOfSubscribers`, `channel`, `channelDescription`, `channelLocation`, `channelJoinedDate`, `channelTotalVideos`, `channelTotalViews` |
| Images | `thumbnailUrl`, `thumbnails`, `channelAvatarUrl`, `channelBannerUrl` |
| Content | `description`, `translatedTitle`, `translatedText`, `tags`, `hashtags`, `category`, `location`, `chapters`, `musicTracks`, `collaborators`, `descriptionLinks`, `sourceLinks` |
| Playback | `playabilityStatus`, `playbackAvailable`, `formats`, `audioTracks`, `heatMap`, `storyboards`, `endscreen` |
| Live state | `streamType`, `scheduledStartTime`, `isLive`, `isLiveContent`, `isUpcoming`, `concurrentViewers`, `isLowLatencyLiveStream`, `latencyClass`, `isPostLiveDvr` |
| Source flags | `allowRatings`, `isCrawlable`, `isPrivate`, `isTvfilmVideo`, `isEmbeddable`, `isChannelVerified`, `isMembersOnly`, `isPaidContent`, `isAgeRestricted`, `isFamilySafe`, `isUnlisted`, `isShortsEligible`, `availableCountries` |
| Captions | `availableLanguages`, `translationLanguages`, `subtitles` |
| Channel profile | `name`, `handle`, `description`, `subscriberCount`, `subscriberCountText`, `videoCount`, `videoCountText`, `viewCount`, `country`, `joinedAt`, `joinedDateText`, `avatarUrl`, `bannerUrl`, `keywordsText`, `rssUrl` |
| Channel destinations | `links`, `tabs`, `channelUrl`, `videosUrl`, `shortsUrl`, `streamsUrl`, `playlistsUrl`, `podcastsUrl`, `coursesUrl` |

</details>

Discovered rows use `detailLevel: "list"`. When the full-details option
succeeds, the row uses `"full"`. Search rows can include `estimatedResultCount`.
This is YouTube's estimate for the query. It can differ from the number of
saved rows after filtering. Related-video rows add `sourceVideoId` and
one-based `recommendationRank`.

Description links preserve video, timestamp, channel, YouTube page, and web
destinations. Sound links identify a source only when YouTube names it.
Channel links have `title`, `url`, and `urlText`; `urlText` preserves the
display and `url` is null when no valid destination is supplied.
`translatedTitle` and `translatedText` contain YouTube's available text and
can match the original.

Format data describes the playback versions available from YouTube. It
includes codecs, dimensions, frame rate, bitrate, audio properties, colour
data, and available size or duration. Media download URLs are not included.
Storyboard URLs can expire. `isMonetized` remains null. Restricted or verified
status is not inferred from missing data.

#### Collections and playlist entries

Collection rows include `collectionType`, `previewVideoIds`,
`descriptionSnippet`, `lastUpdatedText`, and source channel fields when
available. A source channel is the channel whose tab was read; it is not an
inferred owner.

Playlist-entry rows keep `playlistId` and `position`. Repeated videos at
different playlist positions remain separate rows. Nested `playlist` data
includes `collectedEntryCount` and `collectedDurationSeconds`. These totals
cover the entries collected from that playlist. A run limit can reduce the
number saved, so the totals can exceed the saved entries. The duration sum is
null if any collected duration is unknown.
Entry thumbnails describe the video; nested playlist thumbnails describe its
cover. `playlistDetails` has no collected totals.

<details>
<summary>View a recorded course result</summary>

This is a selected-field example from a recorded run. The global row limit
stopped that source, so the row is partial:

```json
{
  "type": "playlist",
  "recordId": "playlist:PLOU2XLYxmsILZnKn6Erxdyhxmc3fxyitP",
  "playlistId": "PLOU2XLYxmsILZnKn6Erxdyhxmc3fxyitP",
  "title": "ML on Android with MediaPipe",
  "url": "https://www.youtube.com/playlist?list=PLOU2XLYxmsILZnKn6Erxdyhxmc3fxyitP",
  "collectionType": "course",
  "sourceChannelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw",
  "videoCount": 9,
  "detailLevel": "full",
  "source": "https://www.youtube.com/@GoogleDevelopers/courses",
  "scrapedAt": "2026-09-23T11:24:23.780Z",
  "schemaVersion": 1,
  "partial": true,
  "partialReason": "item_limit"
}
```

</details>

#### Posts, comments, and chat

Posts keep attachment order in `images`. The attached `video`, `poll`, and
`quiz` are separate fields. Polls preserve choices and total vote labels.
Quiz answers and explanations are included only when the source supplies
them. Empty or missing post text is preserved. Post comment counts do not
include comment bodies; Post comments are not collected.

Comments use `parentCommentId: null` at the top level. A reply identifies its
parent comment. Fields include `replyCount`, `replyLevel`, `isPinned`,
`isCreator`, `isVerified`, `isArtist`, `isHeartedByCreator`, `isEdited`, author
and sponsor badges, `styleRuns`, `links`, and `attachments`. Relative
publication text and edited marks do not establish exact publication or edit
timestamps.

Chat events have an `eventType`, such as `text`, `paid-message`, `paid-sticker`,
`membership`, `membership-gift`, `gift-received`, `engagement`, `mode-change`,
or `deleted`. Available fields include `isUpdated`, `isDeleted`, `isReplay`,
`replayOffsetMs`, `timestamp`, `timestampUsec`, `timestampText`, `amountText`,
`membershipText`, and `stickerUrl`. Updates and removals are separate events;
a removal can have null text and no author data.

Every chat row also has `sourceEventType`. It holds the original YouTube
renderer name for a renderer event, or the action name for another event.
Events without a normalized type use this name as `eventType`.
`messageId` is null when YouTube supplies no message ID; `recordId` remains
stable and nonempty.

`rawEvent` contains the original leaf YouTube action object. For a replay,
the replay wrapper is excluded and `replayOffsetMs` stores its offset.
`rawEvent` is included by default. If you use `fields`, add `rawEvent` to
that list to keep it. `sourceEventType` remains with the identity fields.
Chat limits and `discussion-result` charges count saved event rows, including
control events and events without a message ID.

#### Caption fields

Native captions include source and requested languages, available track and
translation languages, full text, timed segments, and video metadata when
available. `isAutoGenerated` identifies YouTube automatic captions.
`isTranslated` identifies caption translation. Attached captions stay in
`subtitles` on a video or playlist-entry row.

This illustrative segment shows the time units. It is not a recorded caption:

```json
{
  "startMs": 1000,
  "endMs": 2500,
  "text": "First section.",
  "words": [
    { "text": "First", "offsetMs": 0 },
    { "text": "section.", "offsetMs": 500 }
  ]
}
```

Native word offsets are relative to the segment start. Missing word data or
exact offsets remain null.

Saved captions have `subtitleKey`, `subtitleUrl`, and a format URL such as
`vttUrl`. Large caption content moves to `transcriptKey` and `transcriptUrl`;
the row's `fullText`, `segments`, and `formattedText` then become null.

### Collection behaviour

#### Row and content limits

`maxItems` limits rows across the whole run. Each video, collection, Post,
comment, reply, chat event, clip, or standalone transcript track counts as one
row. Attached captions do not add rows. Collection stops before
another source when the global limit is reached.

For search, hashtags, and auto base-channel targets, omitted type limits mean
regular videos use `maxItems`, while Shorts and streams use zero. If any
`limits` value is supplied, omitted types use zero. The Actor collects regular
videos, then Shorts, then streams. Each type limit applies per source and is
capped by the remaining row capacity when results are saved.

An explicit Videos, Shorts, or Streams tab uses `maxItems` when all type
limits are omitted. Once a type limit is supplied, an omitted limit for that
tab is zero. Playlist entries use `limits.videos` or `maxItems`; explicit
playlist mode rejects Shorts and stream limits. Channel and playlist search,
channel collections, Posts, related videos, and chat use `maxItems` directly.

All effective type limits at zero return channel metadata for auto channel
targets, including video-tab URLs. Explicit video-tab modes then return no
rows. Direct video targets still return metadata. Search and hashtag targets
with all type limits at zero return no videos.

For `search.features: ["live"]`, only streams are collected. The limit is
`limits.streams`, then `limits.videos` if supplied, otherwise `maxItems` when
no type limit is set. A Shorts-only limit gives no live rows.

#### Ordering and duplicates

Results follow the source order and supplied target order. There is no global
chronological sort across targets. The Actor removes equivalent input targets
and repeated record IDs. `source` is the first input that produced the row.
Playlist identity includes the position; related-video identity includes the
source video. Recommendations and search results can change between runs.

#### Partial results and unavailable data

A valid search with no matches succeeds with no rows. Results can be partial
when a limit is reached, YouTube stops providing data, or optional work fails.
Rows then have `partial: true` and a reason when available. Failed detail or
caption work can keep the base row with `enrichmentErrors`. Successful
caption tracks remain if another track fails.

Public metadata can exist when playback is unavailable. Check
`playabilityStatus` and `playbackAvailable`. Counts and relative dates are
only exact when the source gives an exact value. The Actor does not infer
poll percentages, monetisation, or private audience data.

`SUMMARY` reports the saved count, stop reason, and outcome for each source.
Mixed or incomplete work has partial status. If all processed sources fail,
the run fails.

#### Costs and restart

Each saved dataset row counts as one billing event in one of two groups:

| Billing event | Saved row types |
|---|---|
| Content result (`content-result`) | `video`, `channel`, `playlist`, `playlist-entry`, `post`, `clip`, `transcript` |
| Discussion result (`discussion-result`) | `comment`, including replies, and `live-chat-message` |

Each group has its own price. See the Actor's Pricing tab in Apify for
current rates and any start or platform charges. The row's `type` identifies
the data it contains; the billing group does not change it.

Attached captions and their file exports are included in the video or
playlist-entry result. They add no billing event. In transcript mode, each
returned language track counts as one content result.

`maxItems` limits the total saved rows across both groups. Content, reply,
track, and chat limits also apply as described above.

An interrupted run can resume with the same input. Start a new run when you
change the input. If saved rows and charges do not match, the run stops to
prevent repeated charges. Use `recordId` to remove duplicates if you combine
exports or repeat a run.

#### Public access and source changes

The Actor collects content available without signing in. Private or
member-only content, owner analytics, moderation tools, and business email
that requires sign-in are not available. Video and audio downloads are not
included.

Chat and replay must be publicly available to the Actor. A chat snapshot
covers a limited period; it does not monitor a stream continuously. Large
collections can be incomplete. YouTube can restrict access, remove content,
or change the available data. `scrapedAt` records the collection time.

# Actor input Schema

## `mode` (type: `string`):

Auto selects a source type from each target. Queries select search. Collection modes read playlists, podcasts, courses, or posts from a channel. Related and liveChat modes take video targets. Clip mode takes public clip IDs or URLs.

## `targets` (type: `array`):

Enter up to 50 YouTube URLs, IDs, handles, or hashtags for the selected mode. Use targets or queries, not both. Each entry must be a string.

## `queries` (type: `array`):

Enter up to 50 search terms or YouTube results URLs. Use only in auto or search mode. Do not also supply targets.

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

Global dataset row limit across all sources. Replies count as rows. Attached captions do not add rows. Zero is invalid.

## `limits` (type: `object`):

Optional limits per discovery source, capped by maxItems. Without limits, collect regular videos. If any limit is set, omitted types use zero. All zero returns channel metadata for auto channel targets. Use only in discovery or playlist modes. In playlist mode, only videos is accepted.

## `includeDetails` (type: `boolean`):

Add full metadata to video, channel, or playlist discovery rows. Default is true in auto and false in explicit list modes. Direct detail modes always request their data. Extra requests can be needed.

## `fields` (type: `array`):

Optional top-level output field names. Identity, status, and file references remain. Selecting fields does not reduce collection, detail requests, or caption work.

## `search` (type: `object`):

Use in auto, search, or hashtag mode. With search filters, a hashtag uses a search for #tag. These controls do not filter channel tabs.

## `channel` (type: `object`):

Use in auto or channelVideos, channelShorts, or channelStreams. Applies to video tabs only.

## `comments` (type: `object`):

Comments mode only. Replies count toward maxItems.

## `captions` (type: `object`):

Supplying this group enables native captions unless mode is set. Without this group, captions are off except in transcript mode. Applies to video results and transcript mode.

## `proxyConfiguration` (type: `object`):

Proxy settings for YouTube requests. Custom proxyUrls are supported. Set useApifyProxy to false for direct access.

## `region` (type: `string`):

Two-letter uppercase country code used for the YouTube response region. Does not set a proxy location or grant access to restricted content.

## `liveChat` (type: `object`):

Use only in liveChat mode. Collect a finite snapshot or available replay messages. Respect returned delays. Missing or restricted chat returns an explicit error.

## Actor input object example

```json
{
  "mode": "search",
  "queries": [
    "machine learning tutorial"
  ],
  "maxItems": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "region": "US"
}
```

# Actor output Schema

## `items` (type: `string`):

Video, channel, playlist, playlist-entry, post, comment, transcript, live-chat-message, or clip rows. Type identifies the row. Caption file references link to the key-value store when available. Selected output fields can reduce each row.

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

Counts, completion status, and failure details for this run.

# 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 = {
    "mode": "search",
    "queries": [
        "machine learning tutorial"
    ],
    "maxItems": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("kaix/youtube-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 = {
    "mode": "search",
    "queries": ["machine learning tutorial"],
    "maxItems": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("kaix/youtube-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 '{
  "mode": "search",
  "queries": [
    "machine learning tutorial"
  ],
  "maxItems": 5
}' |
apify call kaix/youtube-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kaix/youtube-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/PUheyTwXt5GRIuaQe/builds/iHgPZrfsihk6ogPpO/openapi.json
