# YouTube (/docs/youtube)
YouTube [#youtube]
The widest surface on the API after LinkedIn: every tab of a channel, full video detail, threaded comments, two kinds of search, trending, transcripts and subtitle files, the raw audio/video/thumbnail streams, and batch endpoints that take a thousand ids at a time.
Base URL: `/v1/youtube/...`
Choosing between
`/v1/youtube/video/subtitles`
(1cr caption files) and
`/v1/youtube/video/transcript`
(3cr cleaned transcript)? See
[Which endpoint should I use?](/docs/choosing-endpoints.md)
.
How do I get started with YouTube data? [#how-do-i-get-started-with-youtube-data]
1\. Fetch a profile [#1-fetch-a-profile]
```bash
curl "https://www.socialcrawl.dev/v1/youtube/channel?handle=MrBeast" \
-H "x-api-key: sc_your_api_key_here"
```
2\. Fetch their content [#2-fetch-their-content]
```bash
curl "https://www.socialcrawl.dev/v1/youtube/channel/videos?handle=MrBeast" \
-H "x-api-key: sc_your_api_key_here"
```
3\. Read computed fields [#3-read-computed-fields]
When an endpoint supports a computed field and the required source inputs are present, the unified response includes that optional field. Depending on the endpoint, optional fields can include `engagement_rate`, `language`, `content_category`, and `estimated_reach`. See [Computed fields](/docs/computed-fields.md) for formulas, clamping rules, and null semantics.
A channel has five tabs, and each has its own endpoint [#a-channel-has-five-tabs-and-each-has-its-own-endpoint]
`GET /v1/youtube/channel/videos` returns **regular uploads only**. Everything else on a channel lives behind its own call, and this is the single most common cause of "where did the rest of their content go":
| What you want | Endpoint |
| --------------------- | ----------------------------------------- |
| The account snapshot | `GET /v1/youtube/channel` |
| Regular uploads | `GET /v1/youtube/channel/videos` |
| Shorts | `GET /v1/youtube/channel/shorts` |
| Live and past streams | `GET /v1/youtube/channel/lives` |
| Playlists | `GET /v1/youtube/channel/playlists` |
| Text and image posts | `GET /v1/youtube/channel/community-posts` |
All five channel-scoped endpoints take **either `channelId` or `handle`** — one is required — and all cost 1 credit. `channel` additionally accepts a `url`. They page with `continuationToken`, YouTube's own token rather than the universal cursor: pass back what the previous response returned.
`channel/videos` returns light rows by default. Add `includeExtras=true` to fill in `post.engagement.likes`, `post.engagement.comments`, and `post.ext.description` on each row — it slows the response slightly but saves a `video` call per row.
```bash
# Shorts, not uploads
curl "https://www.socialcrawl.dev/v1/youtube/channel/shorts?handle=MrBeast" \
-H "x-api-key: sc_your_api_key_here"
# Uploads with engagement counts attached
curl "https://www.socialcrawl.dev/v1/youtube/channel/videos?handle=MrBeast&includeExtras=true&sort=popular" \
-H "x-api-key: sc_your_api_key_here"
```
`GET /v1/youtube/profile/full` (5cr) collapses the first two rows into one call: the channel, its recent videos, and computed metrics — average engagement rate, posting cadence with the window it was measured over, the top post, and the format mix. `posts` sets how many videos to average over (1-100, default 25). The profile leg is the only critical one, so a failure to fetch videos still returns the profile with null post-dependent metrics and a `legs[]` explaining why.
Two searches, and they return different things [#two-searches-and-they-return-different-things]
* **`GET /v1/youtube/search`** (1cr) is the general one. It returns **every content kind** — videos, channels, playlists, shorts, live streams — with `uploadDate`, `sortBy`, `type`, `duration`, and `region` filters.
* **`GET /v1/youtube/search/advanced`** (1cr) returns **videos only**, but with the full filter set: `published_after` / `published_before` windows, `license`, `category`, `event_type`, `video_caption`, `video_definition`, `video_dimension`, `video_embeddable`, `topic_id`, `location` with `location_radius`, `safe_search`, and more. Reach for it when a plain keyword search is too blunt, and for `search` when you need channels or playlists back too.
`search/advanced` is the one metered endpoint here. The base page is 1 credit; **`includeExtras=true` adds a flat 5 credits** to hydrate the page with view, like, and comment counts, because that hydration is a batch call under the hood. The page caps at 50 results upstream, so the increment is a single flat +5, never a multiple — **1cr or 6cr, nothing in between.**
Two more search surfaces: `GET /v1/youtube/search/hashtag` (1cr) reads a hashtag feed rather than a keyword query, with `type=shorts` to restrict it, and `GET /v1/youtube/search/suggestions` (1cr) returns YouTube's own autocomplete list for a partial query — the cheapest keyword-research call on the platform.
**`search`'s `sortBy=popular` is not a view-count sort.**
It is a popularity-weighted ranking, and results do not come back in descending view order — a
`new york`
search returned 9.5M, then 502.9M, then 66K views in that order, on 4 of 4 test keywords. It also caps the page at roughly 20 results and cannot be combined with
`type=shorts`
(that pair is rejected with a
`400`
). For a view-faithful ordering use
`search/advanced`
with
`order=viewCount&includeExtras=true`
, then sort the page yourself on the returned
`engagement.views`
. Separately,
`search`
's
`duration`
filter is currently not applied by the provider and can degrade the
`type`
filter — filter client-side on
`post.content.duration_seconds`
instead.
```bash
# Videos from a date window, with counts hydrated
curl "https://www.socialcrawl.dev/v1/youtube/search/advanced?query=ai%20agents&published_after=2026-06-01&order=viewCount&includeExtras=true" \
-H "x-api-key: sc_your_api_key_here"
# What does YouTube think people are searching for?
curl "https://www.socialcrawl.dev/v1/youtube/search/suggestions?query=how%20to%20train" \
-H "x-api-key: sc_your_api_key_here"
```
One video, and everything hanging off it [#one-video-and-everything-hanging-off-it]
* **`GET /v1/youtube/video`** (1cr) — the complete record from a video `url`: title, view/like/comment counts, description, tags, duration, channel, publish date. List endpoints return lighter rows without the description or tags, so this is the call when those matter.
* **`GET /v1/youtube/video/comments`** (1cr) — top-level comments with author, text, like count, reply count, and timestamp. `order`, `searchTerm`, and `max_results` narrow it.
* **`GET /v1/youtube/video/comment/replies`** (1cr) — one thread opened up. It takes the **`continuationToken` that `video/comments` returned for that comment**, not a comment id, and you keep paging until no replies remain.
* **`GET /v1/youtube/video/transcript`** (3cr) — the spoken words as timestamped segments with language, word count, and speech rate. A video without captions returns a `404` naming the reason; that is the expected result, not a failure.
* **`GET /v1/youtube/video/subtitles`** (1cr) — the caption *track files* instead: language code and name, format, and a direct download URL, auto-generated tracks included. Cheaper than `transcript` when you want files rather than text.
* **`GET /v1/youtube/video/sponsors`** (10cr) — whether a video carries a paid-promotion disclosure, plus the brands likely sponsoring it, each with supporting evidence and a confidence score. Sponsors are **inferred** from the description, links, promo codes, and transcript, not stated by YouTube, which is why every row carries confidence.
* **`GET /v1/youtube/video/audio`** (5cr), **`GET /v1/youtube/video/files`** (5cr), **`GET /v1/youtube/video/thumbnails`** (1cr) — the downloadable streams. Audio gives mime type, bitrate, quality, sample rate and channels; files gives resolution, quality label, frame rate and bitrate; thumbnails gives every available size with width, height, and format.
The media URLs from
`video/audio`
and
`video/files`
are
**time-limited**
. Fetch them immediately; do not store them in a database and expect them to resolve later. Store the video id and re-request when you need the bytes.
```bash
# The whole comment tree on one video
curl "https://www.socialcrawl.dev/v1/youtube/video/comments?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ&order=top" \
-H "x-api-key: sc_your_api_key_here"
# Then open one thread with the token that came back
curl "https://www.socialcrawl.dev/v1/youtube/video/comment/replies?continuationToken=TOKEN_FROM_PREVIOUS_RESPONSE" \
-H "x-api-key: sc_your_api_key_here"
```
Comments from a date window [#comments-from-a-date-window]
There is no date parameter on `video/comments`, because YouTube exposes none for comments — a server-side filter would read the same pages at the same cost. What makes a window pull work instead is that `order=newest` is an exact sort: rows arrive newest-first on `comment.published_at` with per-second timestamps, and each next page continues strictly older with no overlap.
So to pull, say, the last two years of comments on a video:
1. Request `order=newest` — up to 100 comments per credit.
2. Keep rows where `comment.published_at` is on or after your cutoff.
3. Stop paging when the **last** row of a page is older than the cutoff.
The one row that can defy the sort is a pinned comment, which YouTube serves first regardless of `order` — so filter rows by timestamp (step 2) rather than stopping at the first old row you see, and step 3 stays correct.
```bash
curl "https://www.socialcrawl.dev/v1/youtube/video/comments?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ&order=newest" \
-H "x-api-key: sc_your_api_key_here"
```
The same shape works on the replies side: `video/comment/replies` rows carry the same `comment.published_at`, so apply the cutoff to the rows as you drain each thread.
Playlists and community posts [#playlists-and-community-posts]
`GET /v1/youtube/playlist` and `GET /v1/youtube/playlist/items` (1cr each) both take a `playlist_id` and both return the same videos. The difference is what comes with them: `playlist` attaches view counts and durations, `playlist/items` preserves **playlist order** and fills in when each video was added. Pick by whether you care about the videos or the sequence.
`GET /v1/youtube/community-post` (1cr) takes a single post `url`; `GET /v1/youtube/channel/community-posts` lists them for a whole channel.
Trending [#trending]
`GET /v1/youtube/videos/trending` (1cr) takes `region`, `category`, `language`, and `max_results` — this is the filterable one. `GET /v1/youtube/shorts/trending` (5cr) is a Shorts snapshot and takes **no parameters at all**, so it is one global list rather than a per-country view.
Batch: one call, up to a thousand ids [#batch-one-call-up-to-a-thousand-ids]
Three `POST` endpoints take a JSON body when you already hold ids and want to stop calling the singular endpoint in a loop:
* **`POST /v1/youtube/videos`** — up to 1000 video ids, each row the same object `/v1/youtube/video` returns. Ids that do not resolve are simply absent from the list.
* **`POST /v1/youtube/channels`** — up to 1000 channel ids, each row the same channel object `/v1/youtube/channel` returns, **subscriber count included** (`author.followers`). Ids that do not resolve are absent from the list rather than returned empty, so **join on `author.id`, never on array position** — the response can be shorter than the request. Note that only the singular `channel` endpoint accepts a handle or URL; the batch takes bare `UC…` ids and rejects anything else with a `400` before billing.
* **`POST /v1/youtube/transcripts`** — up to 100 video ids, one transcript row each with the caption language it resolved to and an `ok` or not-found status. A video without captions comes back not-found rather than failing the request, and **failed ids are refunded**. Flat 3cr.
`videos` and `channels` are metered **per chunk of 50 ids at 5 credits a chunk**: 50 ids is 5cr, 200 ids is 20cr, the full 1000 is 100cr. The charge is per chunk regardless of how many ids in it resolve, so there is no per-id refund on these two.
```bash
curl -X POST "https://www.socialcrawl.dev/v1/youtube/transcripts" \
-H "x-api-key: sc_your_api_key_here" \
-H "content-type: application/json" \
-d '{"ids":["dQw4w9WgXcQ","9bZkp7q19f0"],"language":"en"}'
```
Subscriber counts next to search results [#subscriber-counts-next-to-search-results]
Search returns the video, not the creator behind it — YouTube keeps subscriber counts out of its search index, so no search surface on this API carries one and no parameter can add it. Attaching them is a join, and the key is `post.ext.channel_id`.
The subscriber count itself lives on `author.followers`, on both `GET /v1/youtube/channel` (1cr, and cached for 15 minutes, so a repeat lookup of the same channel inside that window is free) and `POST /v1/youtube/channels` (5cr per 50 ids). For a crawl, collect channel ids across the **whole run**, deduplicate, then batch: 23 unique channels resolve in one 5-credit call rather than 23 separate ones.
Which surface hands you the join key matters:
| Search call | Carries `post.ext.channel_id`? |
| -------------------- | ----------------------------------------------- |
| `search?type=videos` | Yes, on every result |
| `search/advanced` | Yes, on every result, no `includeExtras` needed |
| `search?type=shorts` | **No — never.** Nor `post.author.*` |
**Shorts results carry no creator at all.**
YouTube's Shorts shelf ships the video without a channel object, so
`post.author.username`
,
`post.author.display_name`
,
`post.author.avatar_url`
, and
`post.ext.channel_id`
are all null on every
`type=shorts`
result — measured 0 of 98 items across three keywords, and
`includeExtras=true`
does not change it. Recover the creator by feeding the
`post.id`
values to
`POST /v1/youtube/videos`
, which returns
`post.ext.channel_id`
for all of them at 5 credits per 50.
So a page of 25 Shorts costs 1cr to search, 5cr to resolve the 25 videos to 23 unique channels, and 5cr to resolve those channels to subscriber counts — **11 credits for a fully attributed page**. The same page of regular videos skips the middle step: **6 credits.**
```bash
# 1. Regular videos already carry the channel id
curl "https://www.socialcrawl.dev/v1/youtube/search?query=iphone17&type=videos" \
-H "x-api-key: sc_your_api_key_here"
# 2. Shorts don't — resolve post.id → channel id first
curl -X POST "https://www.socialcrawl.dev/v1/youtube/videos" \
-H "x-api-key: sc_your_api_key_here" \
-H "content-type: application/json" \
-d '{"ids":["AeBZjMXIoa4","IbDFupquJf4"]}'
# 3. Either way, deduplicate the channel ids and resolve them in one call
curl -X POST "https://www.socialcrawl.dev/v1/youtube/channels" \
-H "x-api-key: sc_your_api_key_here" \
-H "content-type: application/json" \
-d '{"ids":["UCn9l4gU5mkmmlC2eiVu0LHw","UC_x5XG1OV2P6uZZ5FSM9Ttw"]}'
```
`author.followers`
is YouTube's own published number, and
**YouTube rounds it to three significant figures**
above 1,000 subscribers — you will see 12,800,000 and 85,700 and 1,240, never the digit-exact figure behind them. That rounding is YouTube's, applied to everyone, so there is no exact count being withheld from you; below 1,000 subscribers it is exact. Channels at or above 1,000 subscribers also set
`author.ext.followers_approximate`
to
`true`
. A channel that hides its count entirely sets
`author.ext.hiddenSubscriberCount`
to
`true`
, which is the signal to drop it from a ratio rather than treat a zero as real.
What YouTube data can I access? [#what-youtube-data-can-i-access]
What should I know about YouTube data? [#what-should-i-know-about-youtube-data]
* Everything is `GET` with query parameters except the three batch endpoints (`videos`, `channels`, `transcripts`), which take a `POST` JSON body
* Authentication via `x-api-key` header
* Responses follow the unified SocialCrawl schema
* Channel-scoped endpoints take `channelId` **or** `handle`; video-scoped endpoints take the video `url`; playlist endpoints take `playlist_id`
* Pagination is `continuationToken` on the channel, search, comments, and hashtag endpoints, and `cursor` on `playlist`, `playlist/items`, `videos/trending`, and `search/advanced`. Pass back whatever the previous response returned rather than assuming one name
* `hl` on `channel`, `video`, and the two id-batch endpoints sets the localisation language of the returned metadata
Official YouTube Resources [#official-youtube-resources]
* [YouTube Data API v3](https://developers.google.com/youtube/v3) — Official Google developer documentation
* [YouTube Analytics API](https://developers.google.com/youtube/analytics) — Channel and video analytics