DEV Community

Datawharf Tools
Datawharf Tools

Posted on Edited on

YouTube Data API quota: list a channel's videos for 1 unit, not 100

The YouTube Data API gives a new project 10,000 quota units a day. That sounds like plenty until you notice that one search.list call costs 100 units and returns at most 50 results. If you use search to list a channel's videos, about 100 calls empty the whole day's quota.

You usually don't need search for that. This post shows the cheaper path and a short script that uses it.

Disclosure: I maintain the Apify Actor mentioned at the end. The script is free and stands on its own.

Where the quota goes

From Google's quota cost table (developers.google.com/youtube/v3/determine_quota_cost):

Method Cost per call Max items per call
search.list 100 units 50
channels.list 1 unit 50
playlistItems.list 1 unit 50
videos.list 1 unit 50
commentThreads.list 1 unit 100

Your project's quota page in Google Cloud is the authority for your actual limit. Check it before you plan a big job.

The trick: every channel has an uploads playlist

channels.list with part=contentDetails returns relatedPlaylists.uploads. That is a playlist holding every public upload of the channel, newest first. Reading it with playlistItems.list costs 1 unit per 50 videos, and videos.list then adds views, likes, comment count and duration for another 1 unit per 50.

So "latest 100 videos with stats" costs:

  • 1 unit for the channel lookup (forHandle accepts an @handle directly);
  • 2 units for two playlist pages;
  • 2 units for two videos.list batches.

That's 5 units. The same job through search.list would be at least 200 units, plus the stats calls.

The script

Standard library only. Get a free API key in Google Cloud (enable YouTube Data API v3, then Credentials > Create credentials > API key) and put it in YOUTUBE_API_KEY.

import json, os, sys, urllib.parse, urllib.request

KEY = os.environ["YOUTUBE_API_KEY"]  # your own key from Google Cloud
API = "https://www.googleapis.com/youtube/v3/"
units = 0


def call(method, cost, **params):
    global units
    units += cost
    url = API + method + "?" + urllib.parse.urlencode({**params, "key": KEY})
    with urllib.request.urlopen(url, timeout=30) as r:
        return json.load(r)


def channel(handle):
    """1 unit: profile, stats and the hidden 'uploads' playlist ID."""
    return call("channels", 1, part="snippet,statistics,contentDetails", forHandle=handle)["items"][0]


def latest_uploads(uploads_id, n=50):
    """1 unit per 50 videos, instead of 100 units per search.list page."""
    ids, token = [], None
    while len(ids) < n:
        page = call("playlistItems", 1, part="contentDetails", playlistId=uploads_id,
                    maxResults=50, **({"pageToken": token} if token else {}))
        ids += [i["contentDetails"]["videoId"] for i in page["items"]]
        token = page.get("nextPageToken")
        if not token:
            break
    return ids[:n]


def stats(video_ids):
    """1 unit per 50 videos: views, likes, comments, duration."""
    out = []
    for i in range(0, len(video_ids), 50):
        out += call("videos", 1, part="snippet,statistics,contentDetails",
                    id=",".join(video_ids[i:i + 50]))["items"]
    return out


if __name__ == "__main__":
    sys.stdout.reconfigure(encoding="utf-8")
    c = channel(sys.argv[1] if len(sys.argv) > 1 else "@GoogleDevelopers")
    print(c["snippet"]["title"], "| subscribers:", c["statistics"].get("subscriberCount"),
          "| videos:", c["statistics"].get("videoCount"))
    vids = stats(latest_uploads(c["contentDetails"]["relatedPlaylists"]["uploads"], n=100))
    for v in vids[:3]:
        print(v["snippet"]["publishedAt"][:10], v["statistics"].get("viewCount"), v["snippet"]["title"][:70])
    print(len(vids), "videos with stats for", units, "quota units")
Enter fullscreen mode Exit fullscreen mode

Real output

I ran it on 2026-11-09 against @GoogleDevelopers:

Google for Developers | subscribers: 2680000 | videos: 6125
2026-10-29 13867 Accelerate shipping to scale with Google Antigravity
2026-10-29 3253 ðŸŠī Building an autonomous plant rover with Gemini Live
2026-10-28 41415 Building real-time vision & edge navigation with Gemini Live
100 videos with stats for 5 quota units
Enter fullscreen mode Exit fullscreen mode

Gotchas

  • Subscriber counts are rounded. The API returns them rounded to three significant figures, which is why you see 2680000 rather than an exact number.
  • Uploads order is not strictly publish order. It's close, but scheduled and re-published videos can land out of order. Sort by publishedAt if order matters.
  • Private and deleted videos drop out. videos.list silently omits them, so you can get fewer stats rows than IDs.
  • Missing stats. likeCount is absent when the owner hides it, and commentCount when comments are off. Use .get().
  • When you do need search. Keyword discovery across all of YouTube still needs search.list. Use publishedAfter/publishedBefore windows, because a single query stops at about 500 results.
  • The 30-day rule. YouTube's developer policies require stored API data to be refreshed or deleted within 30 days.

If you would rather not maintain it

I publish a YouTube Data API Actor on Apify that uses these same quota-cheap calls, with your own key as a secret input. It covers channel profiles and uploads, playlists, video details, comments (with optional replies), keyword search and the trending chart per country. Every run saves a QUOTA_REPORT with the units it used.

Python (pip install "apify-client>=3", tokens in APIFY_TOKEN and YOUTUBE_API_KEY):

import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("datawharf-tools/youtube-data-api").call(run_input={
    "apiKey": os.environ["YOUTUBE_API_KEY"],
    "mode": "channel",
    "channel": "@GoogleDevelopers",
    "maxResults": 100,
})
for row in client.dataset(run.default_dataset_id).iterate_items():
    print(row.get("type"), row.get("title"), row.get("viewCount"))
Enter fullscreen mode Exit fullscreen mode

It costs $0.40 per 1,000 rows plus standard Apify usage, and your Google quota stays on your own project. More examples, including JavaScript, are in the examples repo. If the script above covers your needs, use it.

Not affiliated with or endorsed by YouTube or Google.

New to Apify? If you start a paid plan through this referral link, I earn a commission at no extra cost to you.

Top comments (0)