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
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:
{
"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:
{
"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.
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.