YouTubeAnswer

YouTube search API quota: 100 search calls a day, and more

YouTube's Data API default is 100 search.list calls a day, up to 50 results per call. More needs an audit. Side-by-side costs and a paging script.

9 min read

Short answer

  • Google's default for a project that enables the YouTube Data API is 100 search.list calls a day, 1 unit each, in a separate search bucket since 2026-06-01. One call returns up to 50 results, and every extra page is another call (developers.google.com, read 2026-10-16).
  • Google's documented route to more quota is an API compliance audit, started with its Audit and Quota Extension Form. They print no per-call price. Stored data from unauthenticated calls must be deleted or refreshed after 30 days. The search.list reference lists no Shorts parameter.
  • ToolzerHub's /v2/youtube/search returns about 20 results per call for 1 credit, with video, Shorts, upload-date and length filters. In four queries we paged to the end at 517 to 593 videos. $0.99 to $1.90 per 1,000 credits, 100 free credits a month.
On this page

Google's default for a project that enables the YouTube Data API is 100 search.list calls a day. Each call costs 1 unit in its own search bucket, returns up to 50 results, and counts again for every extra page. We read Google's documentation on 2026-10-16.

What Google's pages say the quota is

Google's quota pages (last updated 2026-10-15) say: "Projects that enable the YouTube Data API have a default quota allocation of 100 search.list calls, 100 videos.insert calls, and 10,000 units per day combined for all other endpoints." The search.list reference adds: "Quota impact: 100 calls per day. A call to this method has a quota cost of 1 unit in the Search Queries quota bucket."

Two rules change the arithmetic:

  • Pages count. "If your application calls a method, such as search.list, that returns multiple pages of results, each request to retrieve an additional page of results incurs the estimated quota cost."
  • Invalid requests count. "All API requests, including invalid requests, incur a quota cost of at least one point."

The guides we read for this query on 2026-10-16 all still teach the older model, 100 units a search out of one 10,000-unit pool, and none mentions the June 2026 change. Google's revision history dates that cost to November 11, 2014 ("The quota cost for a call to the search.list method has changed to 100 units") and the bucket change to June 1, 2026: "API calls to the videos.insert and search.list methods will be charged to their own respective quota buckets." The daily count is the same, 100, but the 10,000 units no longer pay for searches.

Page size, filters and Shorts

maxResults takes 0 to 50 and defaults to 5. Google warns that "The API may return fewer items than the requested maxResults per page due to internal sorting or filtering, even if additional results are available", so follow nextPageToken.

Filters include type (video, channel or playlist), publishedAfter, publishedBefore, order, regionCode and videoDuration. There is no Shorts parameter. videoDuration=short means "Only include videos that are less than four minutes long", which is not the same set. For a channel's latest uploads Google says "do not use search.list" and points to playlistItems.list.

How to get more quota, and what you may store

The pages we read describe no way to buy quota and print no per-call price. The route is: "If you would like to request additional quota beyond the default allocation, you must first complete an audit to show that your project is in compliance with the YouTube API Services Terms of Service." You start the audit by submitting the Audit and Quota Extension Form; if you passed an audit in the last 12 months, the same form requests more quota. Google gives no review time.

Storage is limited. Google's developer policies say: "API Clients may temporarily store limited amounts of Non-Authorized Data for as long as is necessary for the purposes of the API Client but not longer than 30 calendar days." Non-Authorized Data is "API Data accessible by an API Client without User Credentials", which is what a search with an API key returns. After 30 days you delete or refresh it.

Search YouTube with ToolzerHub

We make ToolzerHub. Our YouTube API returns public YouTube search results as JSON with one API key and no Google Cloud project. GET /v2/youtube/search costs 1 credit per call (our OpenAPI file, 2026-10-16). This script runs several searches, pages each one, and writes the videos to CSV. Pass shorts instead of video to get Shorts only.

python
import csv
import os
import sys
import time

import requests

URL = "https://api.toolzerhub.com/v2/youtube/search"
HEADERS = {"x-api-key": os.environ.get("TOOLZERHUB_API_KEY", "")}
FIELDS = ["query", "video_id", "title", "channel", "handle", "views", "published", "length", "url"]
calls = 0


def cell(value):
    # Spreadsheets run text that starts with = + - or @ as a formula; an apostrophe keeps it text.
    if isinstance(value, str) and value[:1] in ("=", "+", "-", "@", "\t", "\r"):
        return "'" + value
    return value


def get(params, tries=5):
    # 429 and 503 are retryable; the error body may say how long to wait.
    global calls
    for attempt in range(tries):
        r = requests.get(URL, headers=HEADERS, params=params, timeout=90)
        calls += 1
        if r.status_code == 400:  # bad parameter: skip this query, keep the rest of the run
            print("bad request:", r.json()["error"]["message"].splitlines()[0])
            return None
        if r.status_code not in (429, 503) or attempt == tries - 1:
            r.raise_for_status()
            return r.json().get("data")  # None when there is nothing to return
        try:
            wait = r.json()["error"]["details"]["retryAfter"]
        except (ValueError, KeyError, TypeError):
            wait = 5 * (attempt + 1)
        time.sleep(min(wait, 60))


def videos(entries):
    # Shelves such as the Shorts row hold their items one level down; ads are skipped.
    for e in entries:
        if e.get("type") in ("video", "shorts"):
            yield e
        elif isinstance(e.get("data"), list):
            yield from videos(e["data"])


def row(query, v):
    return {
        "query": query,
        "video_id": v["videoId"],
        "title": (v.get("title") or "").replace("\n", " "),
        "channel": v.get("channelTitle"),
        "handle": v.get("channelHandle"),
        "views": v.get("viewCount"),
        "published": (v.get("publishedAt") or "")[:10],
        "length": v.get("lengthText"),
        "url": f"https://www.youtube.com/watch?v={v['videoId']}",
    }


def main(queries, pages="3", kind="video", out="youtube_search.csv"):
    seen = set()
    with open(out, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=FIELDS)
        writer.writeheader()
        for query in [q.strip() for q in queries.split(",") if q.strip()]:
            params = {"search_term": query, "result_kind": kind}
            for _ in range(int(pages)):
                data = get(params)
                if not data or not data.get("data"):
                    break
                for v in videos(data["data"]):
                    if v["videoId"] not in seen:  # pages can overlap
                        seen.add(v["videoId"])
                        writer.writerow({k: cell(x) for k, x in row(query, v).items()})
                if not data.get("page_cursor"):
                    break
                params["page_cursor"] = data["page_cursor"]
    print(f"wrote {len(seen)} videos to {out} in {calls} calls")


if __name__ == "__main__":
    if len(sys.argv) < 2:
        sys.exit('usage: python youtube_search.py "python tutorial,lofi music" [pages_per_query] [video|shorts] [file.csv]')
    if not HEADERS["x-api-key"]:
        sys.exit("set TOOLZERHUB_API_KEY first")
    main(*sys.argv[1:])

A 200 response with {"data": null} ends that query, a 400 is skipped with a message, and 429 and 503 are retried. cell() puts an apostrophe in front of text that starts with =, +, - or @, so a spreadsheet shows it as text; that is why handles start with '@.

Run the script

bash
export TOOLZERHUB_API_KEY=your_key
python youtube_search.py "python tutorial, nasa artemis ,lofi music,,how to tie a tie" 3 video youtube_search.csv

Output on 2026-10-16, with a stray space and an empty item added to test the stripping:

text
wrote 239 videos to youtube_search.csv in 12 calls

It took 2 seconds: 239 videos from 112 distinct channel handles, median 1,297,907 views. One video matched two queries and was written once. A run with shorts on "python tutorial" wrote 46 Shorts in 2 calls.

What we measured

On 2026-10-16 we ran 10 searches ("python tutorial", "nasa artemis", "lofi music", "how to tie a tie" and six more). All 10 returned data in 0.1 to 1.1 seconds, 20 to 23 items on the first page.

  • A page holds about 20 videos. The default mix adds playlists, courses, channels, a Shorts row and sometimes an ad; result_kind=video returns videos only.
  • Paging has an end. "nasa artemis" ended after 31 calls with 588 videos, and "how to tie a tie" after 29 calls with 517 unique videos (533 returned, 16 repeats). Paged to the end later that day, "sourdough bread" gave 582 unique videos and "python tutorial" 593, each in 31 calls.
  • Filters that worked: result_kind (video, shorts, channel, playlist), published_within (today, week, month, year), length_filter (under 3 minutes, 3 to 20, over 20), sort_order (relevance, popularity), region_code, language_code.
  • result_kind=shorts returned only Shorts, all 60 seconds or shorter. result_kind=video never returned a Shorts item, but short clips still appear as videos: 3 of 60 for "nasa artemis" were 46 to 60 seconds. Check lengthText if you need a hard cut.
  • Search results carry viewCount but no like count; likes come from /v2/youtube/video, 1 credit per call. publishedAt looks derived from text like "2 years ago": older videos get the right year and an approximate day.

Google's default quota and ours, side by side

Google default ToolzerHub
Free allowance 100 search calls a day 100 credits a month
Results per call Up to 50 About 20
Free results Up to 5,000 a day About 2,000 a month
1,000 calls a day Needs an audit 1,000 credits a day

A job of 20 queries with 5 pages each is 100 calls. On Google that is the whole day's quota and up to 5,000 results. On ours it is 100 credits, about 2,000 results, 10 to 19 cents at $0.99 to $1.90 per 1,000 credits. One query paged to its end is about 30 credits: 3 to 6 cents. A run of 1,000 calls a day is 30,000 credits a month, which fits the $79 plan (60,000 credits). On Google it needs the audit and extension request first.

Where Google's API is the better pick

Google's default allowance is far larger than our free tier: about 3,000 search calls in a 30-day month against 100 credits. Its API is official, stable and documented. Use it for uploads, your own channel, captions you own, analytics and anything that needs your users' sign-in, and for under 100 searches a day. Use ours when you need more searches a day than the default before an audit, or a Shorts-only search.

We are not affiliated with Google or YouTube. For transcripts see our YouTube transcript API guide; for another API that moved to per-use pricing, what X charges.

Sources

Get this data from your own code

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