YouTubeAnswer

YouTube API: get a channel's Shorts

The YouTube Data API has no Shorts flag or filter. Our API lists a channel's Shorts, 48 a call, and returns exact views and likes for each one.

5 min read

Short answer

  • The YouTube Data API has no field or filter that marks a video as a Short, and Google closed the request for one as infeasible.
  • Shorts can now run up to three minutes, so filtering uploads by duration mixes them with short regular videos.
  • Our YouTube API lists a channel's Shorts tab, 48 a call at 1 credit, and the video route adds exact views and likes, 20 videos a call.
On this page

The YouTube Data API can't list a channel's Shorts. It has no Shorts field, flag or filter, its only list of a channel's videos is the uploads playlist, and duration does not separate Shorts from regular videos. Our YouTube API reads a channel's public Shorts tab instead, 48 Shorts a call, and the video route adds exact views and likes.

What the YouTube Data API gives you

The YouTube Data API returns a video's duration, dimension and statistics, but nothing that says Short or regular. search.list has videoDuration=short, which means under four minutes, not Shorts. Google's issue tracker entry asking to identify Shorts was closed "Won't Fix (Infeasible)". Shorts uploaded since late 2024 can run up to three minutes, so a duration cut catches short regular videos too. The YouTube Analytics API can split views by Shorts, but only for a channel you own and authorise.

Channel Shorts with ToolzerHub

We make ToolzerHub. Our YouTube API returns public YouTube channels, videos and comments as JSON with one API key and no Google project. These routes do the job here, each 1 credit a call:

  • /v2/youtube/channel/shorts: a channel's Shorts tab, 48 a page
  • /v2/youtube/channel/videos: the Videos tab, 30 a page
  • /v2/youtube/video: up to 20 video IDs a call, with exact views, likes and publish date
bash
export TOOLZERHUB_API_KEY=your_key
curl -H "x-api-key: $TOOLZERHUB_API_KEY" \
  "https://api.toolzerhub.com/v2/youtube/channel/shorts?channel_username=MrBeast&sort_order=newest"

The first Short in data.data, trimmed:

json
{
  "type": "shorts",
  "videoId": "41UzbuW6wJs",
  "title": "Can A Robot Do My Chores?",
  "viewCountText": "59M views"
}

The Shorts tab gives each Short's ID, title and rounded view text, with no date. Pass the IDs to the video route for the rest. The same Short there, trimmed:

json
{
  "id": "41UzbuW6wJs",
  "viewCount": "59242498",
  "likeCount": "945631",
  "publishDate": "2026-10-30",
  "commentCountText": "14K"
}

viewCount and likeCount are exact; comment counts come back rounded. Pass page_cursor from the Shorts response for the next 48.

Compare a channel's Shorts with its videos

The script compares a channel's Shorts and long videos by median views. It reads the newest 48 Shorts and 30 videos of each handle, and fetches exact stats for the Shorts in batches of 20. It writes one row per item to a CSV and prints the median views for each format and how many Shorts beat the video median. Install requests (pip install requests) and save it as youtube_shorts.py.

python
import csv
import os
import statistics
import sys
import time
from datetime import date

import requests

API = "https://api.toolzerhub.com/v2/youtube"
HEADERS = {"x-api-key": os.environ.get("TOOLZERHUB_API_KEY", "")}
FIELDS = ["channel", "type", "video_id", "title", "published", "views", "likes", "length", "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 shorts_details(ids):
    # The video route returns at most 20 videos per call.
    details = {}
    for i in range(0, len(ids), 20):
        for v in get("video", {"video_references": ",".join(ids[i:i + 20])}) or []:
            details[v["id"]] = v
    return details


def main(handles, out="youtube_shorts.csv"):
    with open(out, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=FIELDS)
        writer.writeheader()
        for handle in filter(None, (h.strip().lstrip("@") for h in handles.split(","))):
            try:
                shorts = get("channel/shorts", {"channel_username": handle, "sort_order": "newest"})["data"]
                videos = get("channel/videos", {"channel_username": handle, "sort_order": "newest"})["data"]
                details = shorts_details([s["videoId"] for s in shorts])
            except requests.HTTPError as e:
                print(f"@{handle}: skipped, {e}")
                continue
            rows = []
            for s in shorts:
                d = details.get(s["videoId"], {})
                rows.append({"type": "short", "video_id": s["videoId"], "title": s["title"],
                             "published": d.get("publishDate"), "views": int(d["viewCount"]) if d.get("viewCount") else None,
                             "likes": int(d["likeCount"]) if d.get("likeCount") else None, "length": None,
                             "url": f"https://www.youtube.com/shorts/{s['videoId']}"})
            for v in videos:
                rows.append({"type": "video", "video_id": v["videoId"], "title": v["title"],
                             "published": v.get("publishDate"), "views": v.get("viewCount"), "likes": None,
                             "length": v.get("lengthText"),
                             "url": f"https://www.youtube.com/watch?v={v['videoId']}"})
            for r in rows:
                writer.writerow({"channel": handle, **r})
            short_views = [r["views"] for r in rows if r["type"] == "short" and r["views"]]
            video_views = [r["views"] for r in rows if r["type"] == "video" and r["views"]]
            dates = sorted(r["published"] for r in rows if r["type"] == "short" and r["published"])
            days = (date.fromisoformat(dates[-1]) - date.fromisoformat(dates[0])).days if dates else 0
            video_median = statistics.median(video_views) if video_views else 0
            print(f"@{handle}: {len(shorts)} Shorts ({len(short_views)} with stats) over {days} days, median {statistics.median(short_views or [0]):,.0f} views; "
                  f"{len(video_views)} videos, median {video_median:,.0f} views; "
                  f"{sum(v > video_median for v in short_views)} Shorts above the video median")


if __name__ == "__main__":
    if len(sys.argv) < 2:
        sys.exit('usage: python youtube_shorts.py "@MrBeast,@mkbhd" [out.csv]')
    if not HEADERS["x-api-key"]:
        sys.exit("set TOOLZERHUB_API_KEY first")
    main(*sys.argv[1:])

What four channels showed

python youtube_shorts.py "@MrBeast,@mkbhd,@veritasium,@duolingo" made 20 calls in 72 seconds and saved 192 Shorts and 120 videos. Video views from the Videos tab are rounded, such as 145M, so their medians are close, not exact.

Channel Days the 48 Shorts cover Median Short views Median video views Shorts above the video median
MrBeast 254 147,023,640 145,000,000 25
MKBHD 655 6,284,256 6,100,000 24
Veritasium 326 3,353,557 9,850,000 3
Duolingo 48 534,916 103,000 46

MrBeast's and MKBHD's Short medians land within 4% of their video medians. Veritasium's long videos get about three times the views of its Shorts. Duolingo posts about a Short a day, and 46 of its 48 Shorts beat its median long video, though its 30 videos reach back almost a year. The median like rate on Shorts ran from 1.85% for MrBeast to 3.11% for MKBHD.

What it costs

A channel is 5 credits here: one Shorts page, one videos page and three video batches. The 100 free credits a month cover 20 channels at that depth; $19 buys 10,000 credits that never expire, and plans for higher volume cover weekly tracking of a creator list. Set your key and the first channel is a CSV in under a minute.

For long videos, see get all videos from a YouTube channel. For searches with a Shorts filter, see the YouTube search API quota. For what YouTube shows next to a video, see related videos.

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.