YouTubeAnswer

YouTube API: get related videos

Google removed the related-videos filter from the YouTube Data API. Our API returns a video's related videos, 20 a call, and pages to 60 and more.

5 min read

Short answer

  • The YouTube Data API dropped search.list's relatedToVideoId parameter, so it has no related-videos filter now.
  • A title search is the closest official call, and it returns search results, not the list YouTube shows next to the video.
  • Our YouTube API returns a video's related videos, 20 a call at 1 credit, with a cursor for the next page and a batch route for view counts.
On this page

The YouTube Data API no longer returns related videos. Google deprecated search.list's relatedToVideoId parameter and removed it from the documentation, so the API has no related-videos filter now. Our YouTube API returns a video's related list, 20 videos a call, and pages further with a cursor.

What the YouTube Data API is for

The YouTube Data API lists videos, channels, playlists and comments, and searches them by keyword. search.list used to take relatedToVideoId and return videos like the one you named. YouTube announced the deprecation in June 2023 and stopped supporting the parameter that August. You can search by the video's title, but that returns search results, not the list YouTube shows next to the video.

We make ToolzerHub. Our YouTube API returns public YouTube channels, videos and comments as JSON with one API key and no Google project. Two routes cover this, each 1 credit a call:

  • /v2/youtube/related: a video's related list, about 20 a page, with page_cursor for the next page
  • /v2/youtube/video: up to 20 video IDs a call, with exact views
bash
export TOOLZERHUB_API_KEY=your_key
curl -H "x-api-key: $TOOLZERHUB_API_KEY" \
  "https://api.toolzerhub.com/v2/youtube/related?video_reference=https://www.youtube.com/watch?v=dQw4w9WgXcQ"

The second related video in data.data, trimmed:

json
{
  "type": "video",
  "videoId": "izGwDsrQ1eQ",
  "title": "George Michael - Careless Whisper (Official Video)",
  "channelId": "UCR1LVH-teuA3-Kfy576TYSw",
  "channelTitle": "George Michael",
  "publishedTimeText": "17y ago",
  "lengthText": "5:01"
}

video_reference takes a watch link or an ID. The response's meta block describes the seed video, with its exact viewCount and channelId. Related items carry no view count, so pass their IDs to the video route. Use publishedTimeText for age; publishDate on related items holds a placeholder date.

The script takes video links, reads the pages you ask for, keeps each video once and adds view counts in batches of 20. It writes one row per related video with its rank, channel, views, length and age, and prints how many channels the list draws from. Install requests (pip install requests) and save it as youtube_related_videos.py.

python
import csv
import os
import statistics
import sys
import time

import requests

API = "https://api.toolzerhub.com/v2/youtube"
HEADERS = {"x-api-key": os.environ.get("TOOLZERHUB_API_KEY", "")}
FIELDS = ["seed", "rank", "video_id", "title", "channel", "same_channel", "views", "length", "age", "url"]


def get(op, params, tries=3):
    # Retry rate limits, server errors and dropped connections.
    for attempt in range(tries):
        last = attempt == tries - 1
        try:
            r = requests.get(f"{API}/{op}", headers=HEADERS, params=params, timeout=120)
        except requests.RequestException:
            if last:
                raise
            time.sleep(5 * (attempt + 1))
            continue
        if r.status_code in (429, 500, 502, 503, 504) and not last:
            time.sleep(5 * (attempt + 1))
            continue
        r.raise_for_status()
        return r.json()["data"]


def views(ids):
    # The video route returns at most 20 videos per call.
    counts = {}
    for i in range(0, len(ids), 20):
        for v in get("video", {"video_references": ",".join(ids[i:i + 20])}) or []:
            counts[v["id"]] = int(v["viewCount"]) if v.get("viewCount") else None
    return counts


def main(videos, pages="3", out="youtube_related_videos.csv"):
    with open(out, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=FIELDS)
        writer.writeheader()
        for seed in filter(None, (v.strip() for v in videos.split(","))):
            related, seen, cursor, meta = [], set(), None, {}
            for _ in range(int(pages)):
                data = get("related", {"video_reference": seed, **({"page_cursor": cursor} if cursor else {})})
                meta = meta or data.get("meta") or {}
                for v in data.get("data") or []:
                    if v.get("type") == "video" and v["videoId"] not in seen:
                        seen.add(v["videoId"])
                        related.append(v)
                cursor = data.get("page_cursor")
                if not cursor:
                    break
            counts = views([v["videoId"] for v in related])
            for rank, v in enumerate(related, 1):
                writer.writerow({
                    "seed": meta.get("videoId", seed), "rank": rank, "video_id": v["videoId"], "title": v["title"],
                    "channel": v.get("channelTitle"), "same_channel": v.get("channelId") == meta.get("channelId"),
                    "views": counts.get(v["videoId"]), "length": v.get("lengthText"),
                    "age": v.get("publishedTimeText"), "url": f"https://www.youtube.com/watch?v={v['videoId']}",
                })
            same = sum(v.get("channelId") == meta.get("channelId") for v in related)
            known = [n for n in counts.values() if n]
            print(f"{meta.get('title', seed)[:50]}: {len(related)} related videos from "
                  f"{len({v.get('channelId') for v in related})} channels, {same} from the same channel, "
                  f"median {statistics.median(known or [0]):,.0f} views")


if __name__ == "__main__":
    if len(sys.argv) < 2:
        sys.exit("usage: python youtube_related_videos.py <video links or ids, comma-separated> [pages] [out.csv]")
    if not HEADERS["x-api-key"]:
        sys.exit("set TOOLZERHUB_API_KEY first")
    main(*sys.argv[1:])

Three pages each for 3Blue1Brown's neural network video, Steve Jobs's Stanford speech, Tim Urban's TED talk and Rick Astley's Never Gonna Give You Up made 24 calls in 51 seconds and saved 239 related videos:

bash
python youtube_related_videos.py "https://www.youtube.com/watch?v=aircAruvnKk,https://www.youtube.com/watch?v=UF8uR6Z6KLc,https://www.youtube.com/watch?v=arj7oStGLkU,https://www.youtube.com/watch?v=dQw4w9WgXcQ" 3
Seed video Related saved Channels From the seed's channel Median views Under a year old
3Blue1Brown, neural networks 59 40 10 1,327,378 43
Stanford, Steve Jobs speech 60 47 2 3,632,562 16
TED, Tim Urban 60 34 11 4,359,292 32
Rick Astley 60 50 5 74,577,077 20

Only 2 to 11 of each 60 came from the seed's own channel. 43 of the 59 videos next to the neural network lesson were under a year old, against 16 next to the Jobs speech. Six of Rick Astley's related videos have more views than its 1.83 billion, Gangnam Style at 6.1 billion the largest.

What it costs

A seed video is 6 credits at three pages: three related pages and three view batches. The 100 free credits a month cover 16 videos at that depth; $19 buys 10,000 credits that never expire, and plans for higher volume cover more videos. Set your key and the first related list is a CSV in under a minute.

For a channel's own uploads, see get all videos from a YouTube channel. For keyword results, see the YouTube search API quota, and for search-box suggestions, the YouTube autocomplete API.

We are not affiliated with YouTube or Google.

Get this data from your own code

The YouTube API returns it as JSON with one x-api-key header. 100 free credits monthly.