Developer guide

YouTube API Data API v3: keys, OAuth, quota and uploads in 2026

Checked against Google’s YouTube Data API documentation in September 2026

Short answer: the YouTube Data API v3 is Google’s free API for reading and managing YouTube data. An API key is enough to read public videos and channels. Uploading needs OAuth 2.0 with the youtube.upload scope, and a new project gets 100 uploads a day. Until your project passes Google’s audit, every video it uploads is locked to private.

What is the YouTube Data API?

The YouTube Data API v3 is the REST API behind almost every YouTube integration: upload tools, analytics dashboards, comment moderators, playlist managers and search widgets. It exposes resources such as videos, channels, playlists, playlistItems, commentThreads, captions, thumbnails and search, each with methods like list, insert, update and delete. Requests go to https://www.googleapis.com/youtube/v3/, and file uploads to https://www.googleapis.com/upload/youtube/v3/.

It is not the only YouTube API. The YouTube Analytics API and Reporting API return channel metrics such as watch time and revenue, and the Live Streaming API manages broadcasts. When people say “the YouTube API”, they usually mean the Data API, and that is what this guide covers.

YouTube API key or OAuth 2.0: which one do you need?

Google offers two kinds of credentials, and the choice depends on whose data you touch:

You want to Credential Example methods
Read public videos, channels, playlists, commentsAPI keyvideos.list, channels.list, search.list
Upload a videoOAuth 2.0 (youtube.upload)videos.insert
Edit metadata, set thumbnails, manage playlistsOAuth 2.0 (youtube or youtube.force-ssl)videos.update, thumbnails.set, playlistItems.insert
Post or moderate comments, upload captionsOAuth 2.0 (youtube.force-ssl)commentThreads.insert, captions.insert
Read a user’s private data (their private videos, subscriptions)OAuth 2.0videos.list with mine=true

Sources: registering an application, getting started, videos.insert. Read 29 September 2026.

The rule from Google’s getting-started page: operations that insert, update or delete resources always require user authorization. So an API key never uploads anything. One more trap: service accounts, which work for most Google APIs, do not work with YouTube. A service account cannot be linked to a channel, and the call fails with NoLinkedYouTubeAccount (auth guide). Every upload runs on behalf of a real channel owner who clicked “Allow”.

How to get a YouTube API key

  1. Open the Google Cloud console and create a project (or pick one).
  2. Go to APIs & Services → Library, search for YouTube Data API v3 and click Enable.
  3. Go to APIs & Services → Credentials → Create credentials → API key.
  4. Click the new key and restrict it: limit it to the YouTube Data API v3, and to your server IPs or website referrers. An unrestricted key in a public repo gets its quota burned by someone else.

A key is passed as the key query parameter:

# Public data only: an API key is enough
curl -G "https://www.googleapis.com/youtube/v3/videos" \
  -d part=snippet,statistics \
  -d id=dQw4w9WgXcQ \
  -d key=YOUR_API_KEY

OAuth 2.0 for uploads

For uploads, create an OAuth client ID in the same Credentials page (type “Web application” for a server, “Desktop app” for a script), after filling in the OAuth consent screen. Then send the channel owner to Google’s consent page:

https://accounts.google.com/o/oauth2/v2/auth
  ?client_id=CLIENT_ID.apps.googleusercontent.com
  &redirect_uri=https://yourapp.com/callback
  &response_type=code
  &scope=https://www.googleapis.com/auth/youtube.upload
  &access_type=offline
  &prompt=consent
  &state=RANDOM_STRING

access_type=offline is what gets you a refresh token, so your server can keep uploading after the one-hour access token expires. Exchange the returned code, then refresh as needed:

# Code -> access token (about 1 hour) + refresh token
curl -X POST https://oauth2.googleapis.com/token \
  -d client_id=CLIENT_ID.apps.googleusercontent.com \
  -d client_secret=CLIENT_SECRET \
  -d code=AUTH_CODE \
  -d grant_type=authorization_code \
  -d redirect_uri=https://yourapp.com/callback

# Later: refresh token -> new access token
curl -X POST https://oauth2.googleapis.com/token \
  -d client_id=CLIENT_ID.apps.googleusercontent.com \
  -d client_secret=CLIENT_SECRET \
  -d refresh_token=REFRESH_TOKEN \
  -d grant_type=refresh_token

Ask for youtube.upload if all you do is upload. It is the narrowest scope that allows videos.insert. Setting thumbnails, adding to playlists or posting comments needs youtube or youtube.force-ssl, which ask the user for broader access.

YouTube API quota in 2026

This is where most older guides are now wrong. On 1 June 2026 Google moved the Data API to granular quota buckets, starting with videos.insert and search.list (revision history). Until then, an upload cost 1,600 units out of a shared 10,000, which meant a new project could upload about six videos a day. The default allocation is now:

Quota bucketDefault per project, per day
Video uploads (videos.insert)100 calls, 1 unit each
Search (search.list)100 calls, 1 unit each (every extra page is another call)
Everything else10,000 units, shared by all other methods

What the common calls cost from the 10,000-unit bucket:

MethodUnits
videos.list, channels.list, playlistItems.list1
videos.update50
thumbnails.set50
playlistItems.insert50
commentThreads.insert50
captions.insert400

Sources: quota calculator, videos.insert, revision history. Read 29 September 2026.

Three details that bite in production. Every request costs at least one unit, including invalid ones, so a retry loop on a bad request eats quota. Quotas reset at midnight Pacific Time, not UTC. And quota belongs to the Google Cloud project, not the user: a SaaS with 200 customers shares one pool of 100 uploads a day across all of them. To go higher, you request an extension through the YouTube API Services audit and quota extension form.

The audit: why API uploads stay private

The line at the top of the videos.insert reference is the one to read first: “All videos uploaded via the videos.insert endpoint from unverified API projects created after 28 July 2020 will be restricted to private viewing mode.” Setting privacyStatus to public does not change that, and the call does not fail. The upload succeeds, and the creator later gets an email saying the video is locked as private.

To lift it, the project must pass a compliance audit against the YouTube API Services Terms of Service (audit guide). The same form covers quota extensions. In practice, you describe the app, show how it uses the API, and demonstrate things like how users authorize and how you handle their data. Plan for it early. You can build and test everything while private, but a public launch waits on Google.

How to upload a video with the YouTube API

Use the resumable upload protocol. It survives dropped connections, which matters for files that take minutes to send. videos.insert accepts files up to 256 GB with any video/* MIME type.

1. Start the session, 2. send the file

# 1. Start a resumable session with the metadata
curl -i -X POST \
  "https://www.googleapis.com/upload/youtube/v3/videos?uploadType=resumable&part=snippet,status" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json; charset=UTF-8" \
  -H "X-Upload-Content-Length: 52428800" \
  -H "X-Upload-Content-Type: video/mp4" \
  -d '{
    "snippet": {
      "title": "How we cut our build time in half",
      "description": "Full write-up: https://example.com/blog/builds",
      "tags": ["devops", "ci"],
      "categoryId": "28"
    },
    "status": {
      "privacyStatus": "private",
      "publishAt": "2026-10-02T15:00:00Z",
      "selfDeclaredMadeForKids": false,
      "containsSyntheticMedia": false
    }
  }'
# -> HTTP/1.1 200 OK
#    Location: https://www.googleapis.com/upload/youtube/v3/videos?uploadType=resumable&upload_id=SESSION_ID

# 2. Send the bytes to the session URL
curl -X PUT "SESSION_URL" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: video/mp4" \
  --data-binary @video.mp4
# -> 201 Created with the video resource ({"id": "VIDEO_ID", ...})

The first request carries only the metadata and the file size; the session URL comes back in the Location header. The snippet.title is limited to 100 characters, the description to 5,000 bytes and tags to 500 characters in total, commas included, and none of them may contain < or > (video resource).

Resuming an interrupted upload

# Connection dropped? Ask how much YouTube received
curl -i -X PUT "SESSION_URL" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Length: 0" \
  -H "Content-Range: bytes */52428800"
# -> 308 Resume Incomplete
#    Range: bytes=0-20971519
# Resume from byte 20971520 with Content-Range: bytes 20971520-52428799/52428800

If you upload in chunks instead of one PUT, each chunk except the last must be a multiple of 256 KB and all chunks must be the same size (resumable upload guide). Larger chunks mean fewer requests.

Scheduling with publishAt

The Data API can schedule natively. Upload with status.privacyStatus set to private and status.publishAt set to an ISO 8601 time, as in the example above, and YouTube flips the video to public at that moment. It only works on a private video that has never been published. A time in the past publishes immediately. Remember the audit: for an unverified project the video stays private whatever publishAt says.

Uploading YouTube Shorts via the API

There is no Shorts endpoint and no isShort field. YouTube decides from the file: since 15 October 2024, uploads with a square or vertical aspect ratio that are up to three minutes long are classified as Shorts (YouTube Help). Render at 9:16, keep it at 3:00 or less and upload with videos.insert. A 3:30 vertical video becomes a regular video.

Custom thumbnails

curl -X POST \
  "https://www.googleapis.com/upload/youtube/v3/thumbnails/set?videoId=VIDEO_ID" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: image/jpeg" \
  --data-binary @thumbnail.jpg

thumbnails.set takes a JPEG or PNG, costs 50 units, and needs the youtube or youtube.force-ssl scope. It returns 403 forbidden when the channel is not allowed to use custom thumbnails (the channel owner has to verify it in YouTube first), and 429 uploadRateLimitExceeded when the channel has set too many recently (thumbnails.set).

YouTube API with Python

Google’s client library handles the resumable protocol for you. This follows the official upload sample, updated to the current google-auth-oauthlib flow, and schedules the video for later:

# pip install google-api-python-client google-auth-oauthlib
import time
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from googleapiclient.http import MediaFileUpload

SCOPES = ["https://www.googleapis.com/auth/youtube.upload"]

# One-time: opens a browser for the channel owner to consent.
# Store creds.to_json() and reuse the refresh token afterwards.
flow = InstalledAppFlow.from_client_secrets_file("client_secret.json", SCOPES)
creds = flow.run_local_server(port=0)
youtube = build("youtube", "v3", credentials=creds)

body = {
    "snippet": {
        "title": "3 keyboard shortcuts nobody uses",   # max 100 characters
        "description": "Links in the comments.",       # max 5,000 bytes
        "categoryId": "28",
    },
    "status": {
        "privacyStatus": "private",
        "publishAt": "2026-10-01T21:00:00Z",           # scheduled release
        "selfDeclaredMadeForKids": False,
    },
}

media = MediaFileUpload("short.mp4", mimetype="video/mp4",
                        chunksize=-1, resumable=True)
request = youtube.videos().insert(part="snippet,status",
                                  body=body, media_body=media)

response, retry = None, 0
while response is None:
    try:
        status, response = request.next_chunk()
    except HttpError as e:
        # Google's sample retries these; 403 quotaExceeded is not retryable
        if e.resp.status not in (500, 502, 503, 504) or retry > 5:
            raise
        retry += 1
        time.sleep(2 ** retry)

print("Uploaded:", response["id"])

On a server you will not use run_local_server. Run the web OAuth flow once per channel, store the refresh token, and build Credentials from it on each job.

YouTube API limits at a glance

LimitValue
Uploads100 videos.insert calls per project per day (default)
Searches100 search.list calls per project per day (default)
Other methods10,000 units per project per day (default)
Quota resetMidnight Pacific Time
File size256 GB per video
Title100 characters
Description5,000 bytes
Tags500 characters in total
Unaudited projectUploads locked to private
ShortsSquare or vertical, up to 3 minutes
Resumable chunksMultiples of 256 KB, same size except the last

Sources: quota costs, videos.insert, video resource, resumable uploads, Shorts length. Read 29 September 2026.

Common YouTube API errors and how to fix them

ErrorWhat Google saysFix
403 quotaExceededThe request cannot be completed because you have exceeded your quota.Wait for midnight PT, cut wasted calls (search, failed retries), or request an extension.
400 uploadLimitExceededThe user has exceeded the number of videos they may upload.A per-channel limit, not your project quota. Retry later for that channel.
403 forbiddenAccess forbidden. The request may not be properly authorized.Check the token’s scopes and that it belongs to the channel that owns the video.
401 youtubeSignupRequiredThe user has a Google Account that is not linked to a YouTube channel.The user has to create a channel, then authorize again.
400 invalidPublishAtThe request metadata specifies an invalid scheduled publishing time.Use ISO 8601 and set privacyStatus to private.
400 invalidCategoryIdThe snippet.categoryId property specifies an invalid category ID.Get valid IDs for the region from videoCategories.list.
400 mediaBodyRequiredThe request does not include the video content.Send the file to the /upload/ URL, not the metadata-only endpoint.
403 forbiddenPrivacySettingThe request attempts to set an invalid privacy setting for the video.Use public, unlisted or private.
429 uploadRateLimitExceededThe channel has uploaded too many thumbnails recently.Back off and retry the thumbnails.set call later.
NoLinkedYouTubeAccountReturned when a service account calls the API.Use OAuth with the channel owner’s account.

Sources: global errors, videos.insert errors, thumbnails.set errors, authentication. Read 29 September 2026.

One non-error to watch for: an upload that “works” but never goes public. That is the unaudited-project restriction, not a bug in your code.

The shortcut: YouTube uploads with one Upload-Post call

If YouTube is your product, build on the Data API directly. If you just need videos to go out, the three HTTP calls are the easy part. The work is the Google Cloud project, the consent screen, the audit, the refresh tokens, quota planning and retries. Upload-Post’s YouTube API runs that side: you connect a channel once in the dashboard, and uploads go through Upload-Post’s Google project, so you do not need your own audit or quota extension. You send the file itself:

curl -X POST https://api.upload-post.com/api/upload \
  -H "Authorization: Apikey YOUR_KEY" \
  -F "[email protected]" \
  -F "user=my-brand" \
  -F "platform[]=youtube" \
  -F "title=How we cut our build time in half" \
  -F "description=Full write-up: https://example.com/blog/builds" \
  -F "privacyStatus=public" \
  -F "categoryId=28" \
  -F "[email protected]" \
  -F "youtube_playlist_id=PLxxxxxxxxxxxx" \
  -F "youtube_first_comment=Timestamps below" \
  -F "scheduled_date=2026-10-02T11:00:00" \
  -F "timezone=America/New_York"

That uploads the video, sets the thumbnail, adds it to a playlist, posts a first comment and publishes at 11:00 New York time on 2 October. The fields map to the Data API’s: privacyStatus, categoryId, tags, embeddable, license, selfDeclaredMadeForKids, containsSyntheticMedia, hasPaidProductPlacement, defaultLanguage, defaultAudioLanguage and allowedCountries / blockedCountries. If you prefer YouTube’s own scheduling, send youtube_publish_at (RFC 3339) instead of scheduled_date. Subtitles go in youtube_subtitle_file with youtube_subtitle_language, with indexed fields for several languages. All parameters are in the Upload Video docs.

The same request can send a Short to TikTok and Instagram Reels at the same time. In Python:

import requests

r = requests.post(
    "https://api.upload-post.com/api/upload",
    headers={"Authorization": "Apikey YOUR_KEY"},
    files={"video": open("short.mp4", "rb")},
    data={
        "user": "my-brand",
        "platform[]": ["youtube", "tiktok", "instagram"],
        "title": "3 keyboard shortcuts nobody uses",
        "containsSyntheticMedia": "false",
        "add_to_queue": "true",        # next free slot in your queue
    },
)
print(r.json())
  • Scheduling: scheduled_date with a timezone, up to 365 days ahead, or add_to_queue=true for the next free slot.
  • Large files: pass a public URL in video with async_upload=true and poll Upload Status with the request_id.
  • Thumbnails after the fact: Set YouTube Thumbnail replaces the thumbnail of a video that is already live, Shorts included.
  • Retries without duplicates: send an Idempotency-Key header and a retried request returns the existing job instead of uploading twice.

The Free plan is free forever with no card: 10 uploads a month and 2 profiles, with YouTube and API access included. Paid plans with unlimited uploads start at $24 a month ($16 billed annually). For step-by-step routes, see how to auto-post YouTube Shorts and how to schedule YouTube videos, or connect an AI assistant through the YouTube MCP server. Posting the same clips to TikTok? Read the TikTok API guide.

Frequently asked questions

Is the YouTube Data API free?

Yes. Google does not charge for the YouTube Data API. Access is limited by quota instead of price: by default a project gets 100 video uploads a day, 100 searches a day and 10,000 units a day for everything else. More quota is free too, but you have to pass a compliance audit to get it.

How do I get a YouTube API key?

In the Google Cloud console, create a project, enable the YouTube Data API v3 under APIs & Services, then go to Credentials and choose Create credentials > API key. Restrict the key to the YouTube Data API and to your server IPs or referrers. The key only works for public data; uploads and anything else that touches a channel need OAuth 2.0.

Can I upload videos to YouTube with an API key?

No. Google’s docs say operations that insert, update or delete resources always require user authorization. videos.insert needs an OAuth 2.0 access token with the youtube.upload scope (or youtube / youtube.force-ssl), granted by the owner of the channel. Service accounts do not work either, because they cannot be linked to a YouTube channel.

How many videos can I upload per day with the YouTube API?

Since June 2026, videos.insert has its own quota bucket: 100 calls per day per Google Cloud project by default, at 1 unit each. Before that change an upload cost 1,600 units from the shared 10,000, which capped a new project at about 6 uploads a day. Individual channels can also hit uploadLimitExceeded, which is a per-channel limit and separate from your project quota.

Why are my API uploads stuck as private?

Because your Google Cloud project has not been audited. Google restricts every video uploaded through videos.insert from an unverified API project created after 28 July 2020 to private viewing. The fix is to submit the YouTube API Services audit form and pass the compliance review. Setting privacyStatus to public does not override it.

How do I upload a YouTube Short through the API?

The same way as any other video. There is no Shorts endpoint or flag: YouTube classifies uploads with a square or vertical aspect ratio of up to three minutes as Shorts. Encode the file at 9:16 (for example 1080×1920), keep it at three minutes or less, and call videos.insert.

Can I schedule a YouTube video with the API?

Yes. Upload the video with status.privacyStatus set to private and status.publishAt set to an ISO 8601 date and time. YouTube makes it public at that moment. It only works on private videos that have never been published, and a time in the past publishes the video right away.

How do I set a custom thumbnail with the API?

Call thumbnails.set with the video ID and a JPEG or PNG image once the upload has finished. It costs 50 units. The channel must be allowed to use custom thumbnails, otherwise the call returns 403 forbidden, and too many thumbnail uploads in a short time return 429 uploadRateLimitExceeded.

How much quota does a full upload use?

The upload itself is 1 call from the Video Uploads bucket. Everything around it comes from the 10,000-unit bucket: thumbnails.set 50, playlistItems.insert 50 per playlist, captions.insert 400 per caption track, commentThreads.insert 50 for a first comment, and videos.update 50 if you change metadata later. An upload with a thumbnail, one playlist and one caption track uses about 550 units of the shared bucket.

Is there a YouTube API for Python?

Yes. Google maintains google-api-python-client, and the official upload sample uses it with MediaFileUpload(resumable=True) and next_chunk() so a dropped connection resumes instead of starting over. Authentication uses google-auth-oauthlib for the OAuth flow. The Python example on this page shows the whole upload.

Skip the audit and the quota math

Upload and schedule YouTube videos and Shorts with one API call, and send the same video to TikTok, Instagram and LinkedIn in the same request.

Free forever plan. No credit card. 10 uploads a month.