API Guides

YouTube API Quota Exceeded? 2026 Limits, Costs, and Fixes

YouTube API quota in 2026: three daily buckets, per-call costs, capacity math, quotaExceeded vs uploadLimitExceeded, and how to get more. Plan your quota.
October 05, 2026 by Jesse Eisenbart17 min read
Oil painting of an alpine lake spilling over a small waterfall into a second lake, with a green valley and snowy peaks behind

TLDR: The YouTube API quota in 2026 is three daily buckets per Google Cloud project: 100 videos.insert calls, 100 search.list calls, and 10,000 units for everything else, all resetting at midnight Pacific Time. Uploads no longer cost 1,600 units. quotaExceeded means a bucket is empty; uploadLimitExceeded is a channel limit. More quota means passing YouTube’s compliance audit, not opening a second project.

What is the YouTube API quota in 2026?

The YouTube API quota is the daily allowance Google attaches to every Google Cloud project with the YouTube Data API enabled. Google does not bill for requests; it meters them. As of October 5, 2026, the Quota Calculator lists three default allocations per project:

Bucket Default per day Cost per call
videos.insert (the Video Uploads quota bucket in Google’s reference) 100 calls 1
search.list (the Search Queries quota bucket) 100 calls 1
Everything else, combined 10,000 units Varies by method; see the cost table below

Five rules apply to all three, from the calculator and the quota section of Google’s getting started guide:

  • Reset. Daily quotas reset at midnight Pacific Time, not at a fixed UTC hour or on a rolling 24-hour window.
  • Minimum cost. Every request costs at least one unit, including invalid ones; a rejected call still spends quota.
  • Pagination. Each additional page costs the method’s price again: ten pages of search.list is 10 of the day’s 100 search calls.
  • Live streaming counts. YouTube Live Streaming API methods are part of the Data API and draw quota at the same table’s costs.
  • Where to look. Usage per bucket is on the Quotas page in the Google Cloud console.

A fourth bucket exists too: videos.batchGetStats, added June 3, 2026, returns video statistics at 1 unit per call from its own 10,000-unit daily bucket, per the revision history, though it is not yet in the calculator’s table. The defaults are also subject to change, as the getting started guide notes and the next section shows.

Why do older guides say a YouTube upload costs 1,600 units?

Because they were correct when they were written. The YouTube API quota model has had three eras in under a year, and the revision history dates each one:

  • Until December 4, 2025: one shared bucket of 10,000 units a day. An upload cost about 1,600 units and a search.list call cost 100, so by our arithmetic a project that did nothing else could upload six videos a day.
  • December 4, 2025: Google cut the documented cost of an upload from about 1,600 units to about 100. An archived March 2026 copy of the calculator shows videos.insert and search.list at 100 each, both still drawn from the single 10,000-unit bucket.
  • June 1, 2026: the API moved to what Google calls a granular quota system. videos.insert and search.list got their own buckets at 100 calls a day each, 1 unit per call, and every other method stayed in the existing 10,000-unit bucket. Google’s stated reason: per-method buckets make quota increase requests easier to verify and approve.

So three answers circulate, and Google’s own calculator adds to the confusion: as of October 5, 2026, its auto-generated page summary still says videos.insert has the highest cost at 1,600 points, directly above a table listing it at 100 calls a day, 1 unit each. Guides published as late as March 2026 still charge search.list at 100 units per page. When a tutorial disagrees, check its date, then trust the calculator’s table, not its summary.

How much does each YouTube API call cost?

Google’s getting started guide gives the shape: a list read usually costs 1 unit, a write usually costs 50, a search costs 1 from its own bucket, and an upload costs 1 from its own bucket. The table fills that in for the methods a publishing or analytics integration actually calls, from the calculator as of October 5, 2026:

Method Cost Bucket
videos.insert 1 per call Video Uploads, 100 calls a day
search.list 1 per call Search Queries, 100 calls a day
videos.list, channels.list, playlistItems.list, playlists.list, commentThreads.list, comments.list, activities.list, subscriptions.list, videoCategories.list 1 General, 10,000 units a day
videos.update, videos.delete, videos.rate, thumbnails.set, playlistItems.insert, playlists.insert, commentThreads.insert, comments.insert, comments.setModerationStatus, subscriptions.insert, channels.update, channelBanners.insert, watermarks.set 50 General
captions.list, captions.delete 50 General
captions.insert 400 General
captions.update 450 General
videos.batchGetStats 1 Its own bucket, 10,000 units a day

Two details change how you read it. First, the part parameter has had no effect on cost since July 29, 2020, when Google dropped the per-part surcharge and began charging only the method’s base cost; requesting snippet,statistics,contentDetails costs the same 1 unit as requesting id, so trimming parts saves bandwidth, not quota. Second, captions are the outlier: one captions.insert costs as much as eight thumbnail sets, and an update costs more than an insert, so a pipeline that attaches subtitles to every video should budget for captions first.

How many uploads, searches, and reads fit in the daily YouTube API quota?

The figures below are our arithmetic from Google’s published costs, for a project on the default allocation that does nothing else.

  • Uploads: 100 a day, but the general bucket usually runs out first. A typical pipeline uploads the video (one upload call), sets a thumbnail (50 general units), and adds it to a playlist (50 more): 100 general units per video, so 100 such uploads consume the entire general bucket and leave nothing for reads or comment replies. Skip the playlist step and the same 100 uploads cost 5,000.
  • Captions: 25 a day. At 400 units per captions.insert, 25 caption uploads empty the general bucket by themselves.
  • Searches: 100 calls, at most 5,000 results. search.list accepts a maxResults of 0 to 50, default 5, so 100 calls at the maximum return 5,000 results; a query paged five deep is five of those calls.
  • Reads: hundreds of thousands of videos. videos.list costs 1 unit per call however many IDs you pass. Our analytics sync batches 50 IDs per call, so 10,000 units cover statistics for up to 500,000 videos a day.
  • Comment replies: 200 a day. commentThreads.insert and comments.insert cost 50 each.

Reads are nearly free, uploads are capped by count, and writes empty the general bucket; if a quota alert fires on an upload pipeline, look at the thumbnails and playlist adds first.

What does quotaExceeded mean, and how is it different from uploadLimitExceeded?

When a bucket is empty, the API returns HTTP 403 with the reason quotaExceeded. The body is Google’s standard JSON error, and the reason sits at error.errors[0].reason. The status alone is not enough, because forbidden, an authorization failure, is also a 403. Our YouTube Data API upload guide lists every upload error; the table below covers quota and the problems mistaken for it, drawn from Google’s errors reference, the videos.insert reference, and the OAuth 2.0 guide:

What you see HTTP What it means What to do
quotaExceeded 403 One of your project’s quota buckets is used up for the day Stop calling methods that draw on that bucket until midnight PT; request more if it recurs
uploadLimitExceeded 400 The channel hit YouTube’s daily upload limit, which Google’s reference calls a platform restriction “entirely separate from your Google Cloud project’s API quota” Pause uploads for that channel only; other channels and your quota are unaffected
rateLimitExceeded varies Too many requests in a short time; some methods return it Back off exponentially and retry
forbidden 403 An authorization problem, not quota Check the token, its scopes, and which channel it belongs to
Upload succeeds but the video is private n/a Projects created after July 28, 2020 that have not passed YouTube’s audit can only upload private videos Complete the audit described below
invalid_grant on refresh after about a week n/a An OAuth consent screen in Testing status issues refresh tokens that expire in 7 days Publish the OAuth app so it leaves Testing

The two look-alikes at the bottom cost the most debugging time because neither mentions quota. A private video after a privacyStatus: public upload is the audit restriction; a connection that works for a week and then fails on refresh is the consent screen still in Testing. Neither is fixed by waiting for midnight.

How do you handle quotaExceeded in code?

Retrying quotaExceeded is pointless until the Pacific reset, and retrying uploadLimitExceeded punishes the wrong thing, so the handler has to tell them apart and, better, track spend before Google does. The helper below knows each method’s cost and bucket, refuses a call that would exceed the day’s budget, and turns an error body into an action. Node.js 18 or later, saved as an ES module (a .mjs file, or "type": "module" in package.json) because the usage example uses top-level await:

// Default allocation as of October 5, 2026. Replace with your approved figures.
const LIMIT = { uploads: 100, search: 100, general: 10000 };

// method -> [bucket, units], from Google's Quota Calculator
const COST = {
  'videos.insert': ['uploads', 1],
  'search.list': ['search', 1],
  'videos.list': ['general', 1],
  'channels.list': ['general', 1],
  'playlistItems.list': ['general', 1],
  'thumbnails.set': ['general', 50],
  'playlistItems.insert': ['general', 50],
  'videos.update': ['general', 50],
  'commentThreads.insert': ['general', 50],
  'captions.insert': ['general', 400],
};

const TZ = 'America/Los_Angeles';

// Quota days are Pacific days, so key usage by the Pacific date (YYYY-MM-DD).
const dayKey = () => new Date().toLocaleDateString('en-CA', { timeZone: TZ });

// Milliseconds until the next midnight in Los Angeles. Off by an hour on the
// two days a year the clocks change, which is fine for a pause timer.
function msUntilReset() {
  const parts = {};
  const fmt = new Intl.DateTimeFormat('en-US', {
    timeZone: TZ, hourCycle: 'h23', hour: 'numeric', minute: 'numeric', second: 'numeric',
  });
  for (const p of fmt.formatToParts(new Date())) parts[p.type] = Number(p.value);
  const elapsed = (parts.hour * 3600 + parts.minute * 60 + parts.second) * 1000;
  return 24 * 3600 * 1000 - elapsed;
}

// In-memory for the example. Production code should keep this in Redis or a
// database shared by every worker, or each process counts only its own calls.
const usage = new Map();

function canSpend(method) {
  const [bucket, units] = COST[method] ?? ['general', 1];
  return (usage.get(`${dayKey()}:${bucket}`) ?? 0) + units <= LIMIT[bucket];
}

function recordSpend(method) {
  const [bucket, units] = COST[method] ?? ['general', 1];
  const key = `${dayKey()}:${bucket}`;
  usage.set(key, (usage.get(key) ?? 0) + units);
}

// Map Google's error body to an action. A 403 alone is ambiguous; the reason is not.
function classify(body) {
  const reason = body?.error?.errors?.[0]?.reason;
  if (reason === 'quotaExceeded') return { action: 'pause-bucket', forMs: msUntilReset() };
  if (reason === 'uploadLimitExceeded') return { action: 'pause-channel', forMs: 86_400_000 };
  if (reason === 'rateLimitExceeded') return { action: 'backoff' };
  return { action: 'surface', reason };
}

// Usage: one guarded videos.list call for a batch of IDs.
const method = 'videos.list';
if (!canSpend(method)) {
  throw new Error(`${method} would exceed today's quota; reset in ${msUntilReset()} ms`);
}
const ids = ['VIDEO_ID_1', 'VIDEO_ID_2']; // we send 50 per call
const res = await fetch(
  `https://www.googleapis.com/youtube/v3/videos?part=statistics&id=${ids.join(',')}`,
  { headers: { Authorization: `Bearer ${process.env.YT_ACCESS_TOKEN}` } },
);
recordSpend(method); // invalid requests cost quota too, so record before checking res.ok
const body = await res.json().catch(() => ({}));
if (!res.ok) {
  const outcome = classify(body);
  // pause-bucket: stop every method in that bucket for outcome.forMs
  // pause-channel: stop uploads for this channel only, for outcome.forMs
  // backoff: retry with an exponential delay
  // surface: log and alert; it is not a quota problem
  throw new Error(`YouTube ${res.status}: ${outcome.action}`);
}

The same helper in Python 3.9 or later, which has zoneinfo in the standard library:

import os
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo

import requests

# Default allocation as of October 5, 2026. Replace with your approved figures.
LIMIT = {"uploads": 100, "search": 100, "general": 10_000}

# method -> (bucket, units), from Google's Quota Calculator
COST = {
    "videos.insert": ("uploads", 1),
    "search.list": ("search", 1),
    "videos.list": ("general", 1),
    "channels.list": ("general", 1),
    "playlistItems.list": ("general", 1),
    "thumbnails.set": ("general", 50),
    "playlistItems.insert": ("general", 50),
    "videos.update": ("general", 50),
    "commentThreads.insert": ("general", 50),
    "captions.insert": ("general", 400),
}

PACIFIC = ZoneInfo("America/Los_Angeles")


def day_key() -> str:
    """Quota days are Pacific days, so key usage by the Pacific date."""
    return datetime.now(PACIFIC).strftime("%Y-%m-%d")


def seconds_until_reset() -> float:
    """Seconds until the next midnight in Los Angeles, clock changes included."""
    now = datetime.now(PACIFIC)
    midnight = (now + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
    return midnight.timestamp() - now.timestamp()


# In-memory for the example. Production code should keep this in Redis or a
# database shared by every worker, or each process counts only its own calls.
usage: dict[str, int] = {}


def can_spend(method: str) -> bool:
    bucket, units = COST.get(method, ("general", 1))
    return usage.get(f"{day_key()}:{bucket}", 0) + units <= LIMIT[bucket]


def record_spend(method: str) -> None:
    bucket, units = COST.get(method, ("general", 1))
    key = f"{day_key()}:{bucket}"
    usage[key] = usage.get(key, 0) + units


def classify(body: dict) -> dict:
    """Map Google's error body to an action. A 403 alone is ambiguous; the reason is not."""
    try:
        reason = body["error"]["errors"][0]["reason"]
    except (KeyError, IndexError, TypeError):
        reason = None
    if reason == "quotaExceeded":
        return {"action": "pause-bucket", "for_seconds": seconds_until_reset()}
    if reason == "uploadLimitExceeded":
        return {"action": "pause-channel", "for_seconds": 24 * 3600}
    if reason == "rateLimitExceeded":
        return {"action": "backoff"}
    return {"action": "surface", "reason": reason}


# Usage: one guarded videos.list call for a batch of IDs.
method = "videos.list"
if not can_spend(method):
    raise RuntimeError(f"{method} would exceed today's quota; reset in {seconds_until_reset():.0f} s")
ids = ["VIDEO_ID_1", "VIDEO_ID_2"]  # we send 50 per call
res = requests.get(
    "https://www.googleapis.com/youtube/v3/videos",
    params={"part": "statistics", "id": ",".join(ids)},
    headers={"Authorization": f"Bearer {os.environ['YT_ACCESS_TOKEN']}"},
    timeout=30,
)
record_spend(method)  # invalid requests cost quota too, so record before checking res.ok
try:
    body = res.json()
except ValueError:
    body = {}
if not res.ok:
    outcome = classify(body)
    raise RuntimeError(f"YouTube {res.status_code}: {outcome['action']}")

What to notice:

  • Budget before the call. canSpend checks the bucket the method draws from, so a burst of thumbnail sets cannot silently starve the day’s videos.list reads. Keep LIMIT in configuration and raise it when an extension is approved.
  • Pacific days, not UTC days. Usage is keyed by the date in America/Los_Angeles, so no hard-coded offset, and the pause after quotaExceeded ends at the real reset rather than a flat 24 hours.
  • Reason, not status. classify reads error.errors[0].reason and pauses one channel on uploadLimitExceeded while the others keep publishing. Spend is recorded before the response is checked, because invalid requests cost quota too.
  • Shared state. The map is per process; in production, keep the three counters in Redis or your database so every worker sees the same spend.

How do you use less YouTube API quota?

Most of the YouTube API quota a typical integration burns is avoidable:

  1. Batch IDs into one videos.list call. It costs 1 unit whether the id parameter carries one video ID or a comma-separated list; we batch 50 per call in our analytics sync, turning 50 units into 1.
  2. Never use search.list to list your own channel’s videos. It spends the scarcest bucket, and the search.list reference itself says not to use it for recent uploads because the search index can lag. Call channels.list once, read contentDetails.relatedPlaylists.uploads (the uploads playlist ID), then page through playlistItems.list at 1 unit per page.
  3. Refresh statistics on a schedule that slows with age. Our sync refreshes posts hourly for their first 48 hours, daily up to 30 days, and weekly up to a year.
  4. Validate before you send. Invalid requests cost at least one unit, so check title length (100 characters, no < or >), privacy values, and category IDs locally.
  5. Only make the writes you need. Each thumbnail, playlist add, rating, or comment costs 50 units, the price of 50 reads, so make them optional per post.
  6. Count spend yourself, per Pacific day, per bucket. The Quotas page shows usage but does not stop a worker; keep the counters in shared storage, as in the helper above, and stop at a margin below each limit.
  7. Do not bother trimming part. It has had no effect on cost since July 29, 2020; ask for every part you need in one call.

How do you get a YouTube API quota increase?

Through an audit. Google’s quota and compliance audits guide says quota beyond the default allocation requires first completing an audit that shows your project complies with the YouTube API Services Terms of Service, started through the YouTube API Services Audit and Quota Extension Form. A member of YouTube’s API Services team then contacts you; Google publishes no turnaround time, so plan for the wait. The same form covers a further extension for a project audited within the last 12 months and Google’s periodic re-audits (its first question has an option for keeping current quota without asking for more). A failed audit goes to a separate Appeals Form; a change of control of the project, such as a sale or merger, needs the Change of Control Form.

What the form asks, as of October 5, 2026: organization or individual; a description of your work as it relates to YouTube, 100 to 5,000 characters; target audience and monetization model; your API client’s name, primary URL, privacy policy URL, and optional terms URL; whether it is publicly accessible; demo account credentials with full access and sample data; and up to 10 Google Cloud project numbers per form. Per project you select use case categories, choose a volume band (from fewer than 1,000 requests a day to more than 10 million), and upload evidence: privacy policy screenshots (YouTube sections, Google Privacy Policy link, deletion policies), a homepage screenshot with the privacy policy link and YouTube branding visible, and terms of service documentation. Then, still per project, you enter three separate requests, each a per-day figure with a detailed justification: one for the general bucket, one for search.list, and one for videos.insert. The general figure cannot be spent on those two methods: June’s granular system in paperwork form.

Three rules from YouTube’s Developer Policies shape what you ask for:

  1. Approved quota is tied to the approved use case. A new use case means resubmitting the audit and waiting for approval before spending quota on it.
  2. One API project per API client. Section III.D.1.c requires exactly one project per client, so splitting one app across several projects to multiply the free allocation breaks the policies.
  3. Inactive projects lose quota. YouTube may revoke credentials or cut quota for a project inactive for 90 consecutive days; a reduced project has to reapply.

The audit also lifts the private-only restriction on uploads from projects created after July 28, 2020, so a product that uploads for users needs it regardless of volume.

Do you need your own YouTube API quota to post from your app?

Only if you build the integration directly: your own Google Cloud project, an OAuth consent screen that has left Testing status (Testing issues refresh tokens that expire in 7 days), YouTube’s audit before your users’ uploads can be public, per-bucket spend tracking, and the extension form once the defaults stop fitting. If YouTube is your only network and you plan to run the audit anyway, that is the right call: you keep every setting YouTube offers and deal with Google directly.

If YouTube is one network of several, that work is overhead you can skip. With PostZen, your users connect their channels through a hosted link (GET /v1/connect/{platform} returns an authUrl), and uploads go through our Google Cloud project, so you do not create a project, pass the audit, or file the extension form. Uploads still draw on YouTube quota, ours rather than yours, so if you expect unusually high YouTube volume, tell us before you launch; day to day, what you plan around is our per-minute rate limit, 60 to 2,000 requests a minute by plan, listed on the pricing page.

Our YouTube integration takes video only, one per post; settings.title (or the post’s top-level title) up to 100 characters with no < or >; a required settings.privacyStatus of public, unlisted, or private; optional tags, categoryId, madeForKids, and notifySubscribers; the description from content, up to 5,000 characters; and MP4, MPEG, QuickTime, AVI, WebM, and M4V files up to a hard 2 GB cap. YouTube decides whether a vertical video of about three minutes or less becomes a Short, so there is no Shorts setting. YouTube analytics (views, likes, comments, subscribers) come through the same API, covered in our social media analytics API guide, and an agent can publish and schedule through our MCP server; see the YouTube MCP page and the agents setup page.

The shorter path: one request to every platform

Here is a short vertical video going to YouTube, TikTok, and Instagram Reels in one request, a week out:

curl -X POST https://api.postzen.dev/v1/posts \
  -H "Authorization: Bearer $POSTZEN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "x-request-id: quota-tips-short-2026-10-12" \
  -d '{
    "content": "Five ways to stop burning YouTube API quota, in 90 seconds.",
    "scheduledFor": "2026-10-12T16:00:00Z",
    "mediaItems": [{ "url": "https://cdn.example.com/quota-tips.mp4" }],
    "platforms": [
      {
        "platform": "youtube",
        "accountId": "acc_youtube",
        "settings": {
          "title": "Five ways to stop burning YouTube API quota",
          "privacyStatus": "public"
        }
      },
      {
        "platform": "tiktok",
        "accountId": "acc_tiktok",
        "settings": { "privacyLevel": "publicToEveryone" }
      },
      {
        "platform": "instagram",
        "accountId": "acc_instagram",
        "settings": { "postType": "reel" }
      }
    ]
  }'

Line by line:

  • platforms holds one target per network with its own accountId and settings: YouTube needs privacyStatus and a title (settings.title here, or the post’s top-level title), TikTok needs privacyLevel, and Instagram takes the video as a reel with postType: "reel".
  • mediaItems is a URL that PostZen downloads and re-hosts, up to 100 MB; larger files go through POST /v1/media/presign as a direct upload.
  • scheduledFor must be at least 60 seconds ahead; our social media scheduling API guide covers per-network scheduling.
  • x-request-id makes the create idempotent, so a retry after a timeout returns the original post, not a duplicate.
  • content is the post text; on YouTube it becomes the description.

Webhooks report post.published, post.partially_failed, post.failed, account.needs_reauth, and account.disconnected, so a failed upload reaches you without polling. Users connect through the hosted link without a PostZen account, YouTube analytics come through the same key, rate limits run from 60 requests a minute on Free to 2,000 on Scale, and two SDKs, @postzen/node and postzen-sdk for Python, wrap the same endpoints. Our TikTok API guide covers that network’s review, and the best social media APIs for developers roundup compares the options.

Build directly if YouTube is your only network, and keep the helper above in your worker. Otherwise, this is the case we built our social media API for: one request, one set of rate limits, and no YouTube quota paperwork on your side.

Frequently asked questions

What is the YouTube API quota limit per day?

As of October 5, 2026, every Google Cloud project with the YouTube Data API enabled gets three default daily allocations: 100 videos.insert calls, 100 search.list calls, and 10,000 units shared by every other method. All three reset at midnight Pacific Time. Google notes that the defaults are subject to change, and anything above them requires passing YouTube's compliance audit first.

How much YouTube API quota does a video upload use?

One call from the Video Uploads bucket, which allows 100 uploads a day by default. Until December 4, 2025, an upload cost about 1,600 units from the shared 10,000-unit pool, which is why older guides say about six uploads a day. The calls around an upload still draw general quota: a thumbnail is 50 units, a playlist add is 50, and a caption upload is 400.

What does quotaExceeded mean in the YouTube Data API?

quotaExceeded is the reason in a 403 error body, and it means one of your project's quota buckets is used up for the current Pacific day. Read it from error.errors[0].reason, because a 403 alone can also mean forbidden, an authorization problem. Stop calling methods that draw on the empty bucket until the midnight PT reset, and request more quota if it keeps happening.

When does the YouTube API quota reset?

At midnight Pacific Time, every day, for all of a project's quota buckets. That is a Pacific wall-clock time rather than a fixed UTC hour, so compute the next reset in the America/Los_Angeles time zone instead of hard-coding an offset. The helper in this guide does exactly that, and pauses an exhausted bucket until the next reset rather than for a flat 24 hours.

How do I get a YouTube API quota increase?

Submit the YouTube API Services Audit and Quota Extension Form. It audits your project against the YouTube API Services Terms of Service, and a member of YouTube's API Services team then contacts you; Google publishes no turnaround time. The form asks for a separate per-day figure and justification for the general bucket, search.list, and videos.insert. Developers audited within the last 12 months use the same form for an extension.

Can I use several Google Cloud projects to get more YouTube API quota?

No. YouTube's Developer Policies, section III.D.1.c, require exactly one API project per API client, so splitting one app's traffic across several projects to multiply the default quota breaks the policies. The sanctioned route is the Audit and Quota Extension Form, which accepts up to 10 project numbers per submission for developers who run several distinct API clients.