# Youtube Connector

> Access YouTube data using curl, jq, and yt-dlp. Use this skill whenever the user wants to search YouTube videos, get video details or statistics, fetch channel information, read video transcripts/captions, browse comments, or explore playlists. Also use when the user mentions YouTube in any research, analysis, or data-gathering context — even if they don't explicitly say "use YouTube API."

- Skill: `shellydeng08/youtube-connector` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add shellydeng08/youtube-connector`
- Raw SKILL.md: https://api.skillmd.com/api/skills/shellydeng08/youtube-connector/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Research & Search
- Author: ShellyDeng08 (https://skillmd.com/u/shellydeng08)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/shellydeng08/youtube-connector

---


# YouTube Connector

Access YouTube data from the command line. No MCP server needed.

This skill supports **two modes**:

1. **API Mode** (recommended) — uses YouTube Data API v3 via `curl`. Requires a free `YOUTUBE_API_KEY`. Structured, reliable, and quota-managed.
2. **yt-dlp Mode** (no API key) — uses `yt-dlp` for everything. Zero configuration, but higher risk of IP-based rate limiting from YouTube.

**Choose API Mode when** `YOUTUBE_API_KEY` is set. **Fall back to yt-dlp Mode** when no API key is available.

## Prerequisites

- `curl` and `jq` installed (standard on most systems)
- `yt-dlp` installed: `brew install yt-dlp` or `pip install yt-dlp`
- (Optional but recommended) `YOUTUBE_API_KEY` environment variable — get a free key from [Google Cloud Console](https://console.cloud.google.com/apis/credentials)

## IMPORTANT: Check Tools Before Running Any Command

**Before executing ANY command in this skill, you MUST run these checks first:**

```bash
# Check all required tools in one command
which curl && which jq && which yt-dlp && echo "---YOUTUBE_API_KEY: ${YOUTUBE_API_KEY:+set (${#YOUTUBE_API_KEY} chars)}${YOUTUBE_API_KEY:-NOT SET}"
```

**How to interpret the results and what to do:**

| Result | Meaning | Action |
|--------|---------|--------|
| `curl: not found` | curl is missing | Ask user to install curl |
| `jq: not found` | jq is missing | Ask user to run `brew install jq` (macOS) or `apt install jq` (Linux) |
| `yt-dlp: not found` | yt-dlp is NOT installed | **You CANNOT use any yt-dlp command.** Ask user to run `brew install yt-dlp` (macOS) or `pip install yt-dlp`. Do NOT attempt yt-dlp commands — they will fail with "command not found", not "rate limited". |
| `YOUTUBE_API_KEY: NOT SET` | No API key configured | You must use yt-dlp mode (if yt-dlp is installed). Tell the user that API mode is unavailable. |
| `YOUTUBE_API_KEY: set` | API key is available | Use API mode (recommended). |

**Common mistake**: If yt-dlp is not installed and you run yt-dlp commands, the shell returns "command not found" — this is NOT a rate limit or network error. **Do not tell the user they are being rate-limited when the tool is simply not installed.**

## API Quota Awareness

YouTube Data API v3 has a default quota of **10,000 units/day**. Costs vary by endpoint:

| Operation | Cost |
|-----------|------|
| search.list | 100 units |
| videos.list | 1 unit |
| channels.list | 1 unit |
| commentThreads.list | 1 unit |
| playlistItems.list | 1 unit |
| playlists.list | 1 unit |

**Search is expensive.** Prefer direct video/channel lookups when IDs are known. A single search costs 100x more than a detail lookup.

## Base URL

All API requests use:
```
https://www.googleapis.com/youtube/v3
```

## Endpoints

### Search Videos

```bash
curl -s "https://www.googleapis.com/youtube/v3/search?part=snippet&q=QUERY&type=video&maxResults=10&key=$YOUTUBE_API_KEY" | jq '.items[] | {videoId: .id.videoId, title: .snippet.title, channel: .snippet.channelTitle, published: .snippet.publishedAt, description: .snippet.description}'
```

Additional search parameters:
- `&type=channel` or `&type=playlist` to search for channels/playlists
- `&order=date` (default: relevance) — also: `rating`, `viewCount`
- `&publishedAfter=2024-01-01T00:00:00Z` — filter by date
- `&channelId=CHANNEL_ID` — search within a specific channel
- `&maxResults=N` — 1 to 50 (default 5)

### Get Video Details

```bash
curl -s "https://www.googleapis.com/youtube/v3/videos?part=snippet,statistics,contentDetails&id=VIDEO_ID&key=$YOUTUBE_API_KEY" | jq '.items[0] | {title: .snippet.title, channel: .snippet.channelTitle, published: .snippet.publishedAt, description: .snippet.description, duration: .contentDetails.duration, views: .statistics.viewCount, likes: .statistics.likeCount, comments: .statistics.commentCount}'
```

Multiple videos: `&id=ID1,ID2,ID3` (comma-separated, up to 50).

The `duration` field is ISO 8601 format (e.g., `PT1H2M30S` = 1 hour, 2 minutes, 30 seconds).

### Get Channel Info

By channel ID:
```bash
curl -s "https://www.googleapis.com/youtube/v3/channels?part=snippet,statistics,contentDetails&id=CHANNEL_ID&key=$YOUTUBE_API_KEY" | jq '.items[0] | {title: .snippet.title, description: .snippet.description, subscribers: .statistics.subscriberCount, videos: .statistics.videoCount, views: .statistics.viewCount, uploads_playlist: .contentDetails.relatedPlaylists.uploads}'
```

By handle (e.g., @mkbhd):
```bash
curl -s "https://www.googleapis.com/youtube/v3/channels?part=snippet,statistics,contentDetails&forHandle=@HANDLE&key=$YOUTUBE_API_KEY" | jq '.items[0] | {title: .snippet.title, description: .snippet.description, subscribers: .statistics.subscriberCount, videos: .statistics.videoCount, views: .statistics.viewCount, uploads_playlist: .contentDetails.relatedPlaylists.uploads}'
```

The `uploads_playlist` ID can be used with the playlist items endpoint to list all channel uploads.

### Get Video Comments

```bash
curl -s "https://www.googleapis.com/youtube/v3/commentThreads?part=snippet,replies&videoId=VIDEO_ID&maxResults=20&order=relevance&key=$YOUTUBE_API_KEY" | jq '.items[] | {author: .snippet.topLevelComment.snippet.authorDisplayName, text: .snippet.topLevelComment.snippet.textDisplay, likes: .snippet.topLevelComment.snippet.likeCount, published: .snippet.topLevelComment.snippet.publishedAt, replyCount: .snippet.totalReplyCount}'
```

Parameters:
- `&order=relevance` (default) or `&order=time`
- `&maxResults=N` — 1 to 100 (default 20)
- `&pageToken=TOKEN` — for pagination (from `nextPageToken` in response)

### Get Playlist Items

```bash
curl -s "https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId=PLAYLIST_ID&maxResults=50&key=$YOUTUBE_API_KEY" | jq '.items[] | {position: .snippet.position, title: .snippet.title, videoId: .snippet.resourceId.videoId, channel: .snippet.channelTitle, published: .snippet.publishedAt}'
```

Use `&pageToken=TOKEN` for pagination (from `nextPageToken` in response).

### List Channel Playlists

```bash
curl -s "https://www.googleapis.com/youtube/v3/playlists?part=snippet,contentDetails&channelId=CHANNEL_ID&maxResults=50&key=$YOUTUBE_API_KEY" | jq '.items[] | {playlistId: .id, title: .snippet.title, description: .snippet.description, videoCount: .contentDetails.itemCount, published: .snippet.publishedAt}'
```

### Get Video Transcript

Transcripts are not available via the Data API. Use `yt-dlp` instead:

```bash
yt-dlp --write-sub --write-auto-sub --sub-lang en --skip-download --sub-format json3 -o "/tmp/yt-sub" "https://www.youtube.com/watch?v=VIDEO_ID"
```

This downloads the subtitle file to `/tmp/yt-sub.en.json3`. Parse it:

```bash
cat /tmp/yt-sub.en.json3 | jq '[.events[] | select(.segs) | {time: (.tStartMs / 1000), text: ([.segs[].utf8] | join(""))}]'
```

For plain text only (no timestamps):
```bash
cat /tmp/yt-sub.en.json3 | jq '[.events[] | select(.segs) | [.segs[].utf8] | join("")] | join(" ")' -r
```

To list available subtitle languages:
```bash
yt-dlp --list-subs "https://www.youtube.com/watch?v=VIDEO_ID"
```

Clean up after reading:
```bash
rm -f /tmp/yt-sub.en.json3
```

## Pagination

Endpoints that return lists support pagination via `pageToken`. The response includes:
- `nextPageToken` — pass as `&pageToken=VALUE` to get the next page
- `prevPageToken` — for going back
- `pageInfo.totalResults` — total count
- `pageInfo.resultsPerPage` — items per page

## Common Patterns

### Get all videos from a channel
1. Get the channel's uploads playlist ID from the channel endpoint (`uploads_playlist`)
2. Paginate through playlistItems using that playlist ID

### Search then get details
1. Search to find video IDs (100 quota units)
2. Batch-fetch details with comma-separated IDs (1 quota unit for up to 50 videos)

### Extract video ID from URL
YouTube URLs come in several forms. Extract the video ID:
```bash
# From various URL formats (works on both macOS and Linux)
echo "https://www.youtube.com/watch?v=dQw4w9WgXcQ" | sed -n 's/.*[?&]v=\([^&]*\).*/\1/p'
echo "https://youtu.be/dQw4w9WgXcQ" | sed -n 's/.*youtu\.be\/\([^?]*\).*/\1/p'
```

## Error Handling

API errors return JSON with an `error` object:
```bash
curl -s "..." | jq '.error // empty | {code, message: .message, reason: .errors[0].reason}'
```

Common errors:
- `403 quotaExceeded` — daily quota exhausted
- `403 forbidden` — API key invalid or API not enabled
- `404 videoNotFound` — video doesn't exist or is private
- `400 invalidParameter` — check parameter values

---

## yt-dlp Mode (No API Key)

**Prerequisite: Verify `yt-dlp` is installed before using ANY command below.** Run `which yt-dlp` — if it returns "not found", you MUST ask the user to install it first (`brew install yt-dlp` or `pip install yt-dlp`). Do NOT proceed without it.

When `YOUTUBE_API_KEY` is not set, use `yt-dlp` for all operations. This avoids needing any API key but comes with trade-offs.

> **Risk Warning**: yt-dlp scrapes YouTube directly without official API authorization. YouTube may:
> - **Rate limit or block your IP** after too many requests in a short period
> - **Break without notice** when YouTube changes its frontend (yt-dlp updates usually fix this within days)
> - **Return incomplete data** compared to the official API (e.g., exact subscriber counts may be rounded)
>
> For occasional/personal use this is fine. For heavy or production use, get a free API key instead.

### Search Videos (yt-dlp)

```bash
yt-dlp "ytsearch10:QUERY" --dump-json --flat-playlist 2>/dev/null | jq -s '[.[] | {videoId: .id, title: .title, channel: .channel, views: .view_count, duration: .duration_string, url: .url}]'
```

Replace `10` in `ytsearch10` with the desired number of results (max ~50).

### Get Video Details (yt-dlp)

```bash
yt-dlp --dump-json --skip-download "https://www.youtube.com/watch?v=VIDEO_ID" 2>/dev/null | jq '{title, channel, upload_date, duration_string, view_count, like_count, comment_count, description: (.description[:500])}'
```

### Get Channel Info (yt-dlp)

```bash
yt-dlp --dump-json --flat-playlist --playlist-items 0 "https://www.youtube.com/@HANDLE" 2>/dev/null | jq '{channel, channel_id, channel_url, channel_follower_count}'
```

### List Channel Videos (yt-dlp)

```bash
yt-dlp --dump-json --flat-playlist "https://www.youtube.com/@HANDLE/videos" 2>/dev/null | jq -s '[.[:20] | .[] | {videoId: .id, title: .title, duration: .duration_string, views: .view_count}]'
```

### Get Video Comments (yt-dlp)

```bash
yt-dlp --write-comments --dump-json --skip-download "https://www.youtube.com/watch?v=VIDEO_ID" 2>/dev/null | jq '[.comments[:20] | .[] | {author: .author, text: (.text[:500]), likes: .like_count, time: .timestamp}]'
```

> Note: `--write-comments` can be **very slow** on videos with many comments. Consider limiting with the API mode for comment-heavy videos.

### Get Video Transcript (yt-dlp)

Same as API mode — transcripts always use yt-dlp:

```bash
yt-dlp --write-sub --write-auto-sub --sub-lang en --skip-download --sub-format json3 -o "/tmp/yt-sub" "https://www.youtube.com/watch?v=VIDEO_ID"
cat /tmp/yt-sub.en.json3 | jq '[.events[] | select(.segs) | {time: (.tStartMs / 1000), text: ([.segs[].utf8] | join(""))}]'
rm -f /tmp/yt-sub.en.json3
```

### Get Playlist Videos (yt-dlp)

```bash
yt-dlp --dump-json --flat-playlist "https://www.youtube.com/playlist?list=PLAYLIST_ID" 2>/dev/null | jq -s '[.[] | {videoId: .id, title: .title, duration: .duration_string, channel: .channel}]'
```

