YouTube API Upload Video: Quota, Shorts, and Code
To upload a video with the YouTube API, call videos.insert on the YouTube Data API v3 with an OAuth token that carries the youtube.upload scope, and send the file as a resumable upload. Each upload costs 1 unit from its own Video Uploads quota bucket, which defaults to 100 uploads a day per project. Shorts have no flag. Adeli wraps the whole flow in one POST request.
You upload a video with the YouTube API by calling videos.insert on the YouTube Data API v3, authorized with an OAuth 2.0 token from the channel owner. Send the metadata and the file to https://www.googleapis.com/upload/youtube/v3/videos, preferably as a resumable upload so a dropped connection doesn't cost you the whole file. Below: the request, the quota, Shorts, uploads from a URL, and the errors to handle.
How does the YouTube API upload a video?
One method does it: videos.insert. It takes a video resource (title, description, privacy) plus the media file in the same request. Google's videos.insert reference sets the limits:
| YouTube Data API v3 | |
|---|---|
| Endpoint | POST https://www.googleapis.com/upload/youtube/v3/videos |
| Required parameter | part, such as snippet,status |
| Max file size | 256 GB |
| Accepted MIME types | video/*, application/octet-stream |
| Quota cost | 1 unit in the Video Uploads bucket |
| Default limit | 100 calls a day per project |
If you leave status.privacyStatus unset, the video defaults to public, though Google notes some uploads default to private based on the creator's account settings. Set it explicitly every time.
What do you need before you start?
- A Google Cloud project with the YouTube Data API v3 enabled.
- An OAuth 2.0 client ID. Uploads act on a user's channel, so an API key alone won't work.
- One upload scope.
videos.insertaccepts any one ofyoutube.upload,youtube,youtubepartner, oryoutube.force-ssl. Google's upload guide describesyoutube.uploadas allowing uploads to the authenticated user's channel but no other access, so it's the narrowest choice. - A channel. The Google account that signs in has to own a YouTube channel, or a Brand Account channel.
Step 1: Start a resumable upload session
POST the metadata first, and Google answers with a session URI. The resumable upload guide uses uploadType=resumable:
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: $FILE_SIZE_BYTES" \
-H "X-Upload-Content-Type: video/mp4" \
-d '{
"snippet": { "title": "Launch demo", "description": "Two-minute walkthrough", "categoryId": "22" },
"status": { "privacyStatus": "unlisted", "selfDeclaredMadeForKids": false }
}'A 200 response carries the session URI in its Location header. Save it. Every later request goes there.
Step 2: Upload the file
PUT the raw bytes to the session URI. Content-Length and Content-Type must match the values you declared in Step 1:
curl -X PUT "$SESSION_URI" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Length: $FILE_SIZE_BYTES" \
-H "Content-Type: video/mp4" \
--data-binary "@launch-demo.mp4"A 201 Created returns the full video resource, including the new video ID.
Step 3: Resume an interrupted upload
Ask how many bytes arrived, then send the rest. Google treats a lost connection or a 500, 502, 503, or 504 as resumable and tells you to retry with exponential backoff.
- Check status. Send an empty
PUTto the session URI withContent-Length: 0andContent-Range: bytes */$FILE_SIZE_BYTES. - Read the
308 Resume Incompleteresponse. ItsRangeheader, such asbytes=0-999999, lists the bytes Google already has. NoRangeheader means nothing arrived. - Send the remainder.
PUTthe bytes from the next byte onward withContent-Range: bytes 1000000-$LAST_BYTE/$FILE_SIZE_BYTES.
An expired session URI returns 404, and you start a new session from Step 1.
What is the YouTube API upload quota?
100 uploads a day per project, at 1 unit each, in a bucket of their own. Google's quota cost page gives every project a default of 100 search.list calls, 100 videos.insert calls, and 10,000 units a day for all other endpoints combined. Quotas reset at midnight Pacific Time.
Both numbers are recent, so older tutorials may quote different ones. Google's revision history shows two moves:
| Date | What changed |
|---|---|
| December 4, 2025 | An upload's cost dropped from about 1,600 units to about 100 units |
| June 1, 2026 | videos.insert and search.list moved to their own quota buckets |
Calls around the upload still draw from the 10,000-unit pool. thumbnails.set costs 50 units, and videos.list, which you'll poll for processing status, costs 1. A request that fails still costs at least one unit.
Need more than 100 a day? Google requires an audit against the YouTube API Services Terms of Service before it grants extra quota, per its quota and compliance audits page. You apply through the Audit and Quota Extension Form. Running out mid-day returns 403 quotaExceeded.
What is the YouTube API upload limit?
There are two: your project's quota and each channel's own cap. They fail differently, so handle them separately:
| Limit | Applies to | Error |
|---|---|---|
| Video Uploads quota | Your Google Cloud project | 403 quotaExceeded |
| Channel upload limit | One YouTube channel | 400 uploadLimitExceeded |
Google's reference describes uploadLimitExceeded only as the user having "exceeded the number of videos they may upload." It doesn't publish the number, so treat it as a retry-later error for that one channel and keep uploading for the others.
How do I upload YouTube Shorts with the API?
Upload a Short with videos.insert, like any other video. The API has no Shorts flag or Shorts endpoint, and the videos.insert reference doesn't mention Shorts. YouTube decides from the file: its Shorts upload help lists videos up to 3 minutes with a square or vertical aspect ratio.
Check the file before upload. Duration and aspect ratio decide it, so validate both in your pipeline before you spend a quota unit.
Can the YouTube API upload a video from a URL?
No. videos.insert needs the file bytes in the request. Send metadata without media and Google returns mediaBodyRequired, "The request does not include the video content." To post a video hosted elsewhere, your server has to download it and then stream it into the resumable upload from Step 2.
Why are my API uploads private?
Check your request and the channel's settings first. Since July 2020, Google has locked uploads from unaudited API projects to private until the project passes a YouTube API Services audit. Adeli's YouTube app has passed that audit, so uploads through Adeli get the visibility you ask for.
Things to check, in order:
status.privacyStatus. Read it back from the response. It's the visibility YouTube applied.status.publishAt. A scheduled video stays private until that time.- The creator's account settings. Google notes these can default an upload to private.
Is there a faster way to upload videos to YouTube?
Yes: send one request to an API that already runs the YouTube integration. Adeli's YouTube API holds the Google developer app, carries Google's review, and refreshes each channel's tokens. Your customers connect their own channels through Adeli's hosted Google sign-in, and you never touch an OAuth token.
Publish from a URL. POST /api/v1/posts takes a public HTTPS video.url of up to 1 GB, and Adeli downloads it and does the upload.
curl https://app.tryadeli.com/api/v1/posts \
-H "Authorization: Bearer $ADELI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"platform": "youtube",
"accountId": "'"$ACCOUNT_ID"'",
"title": "Launch demo",
"privacy_status": "unlisted",
"made_for_kids": false,
"video": { "url": "https://cdn.example.com/launch-demo.mp4" }
}'What Adeli handles for you:
- Big files.
POST /api/v1/posts/youtubetakes a multipart file of up to 4 GiB, plus an optional thumbnail. A refused thumbnail comes back as a warning, not a failed upload. - Shorts. There's no separate Shorts call. A vertical or square video up to three minutes becomes a Short.
- Processing status. The publish returns
202with apublishId. PollGET /api/v1/posts/youtube/statusuntilstatusispublishedorfailed, with YouTube'sfailureReasonwhen it fails. - Requested vs. applied visibility. The response reports
requestedPrivacyandappliedPrivacy, and setsprivacyRestrictedtotruewhen YouTube applied something other than what you asked for. - Separate limit errors. A spent daily quota returns
429 quota_exhaustedwith the reset time in the message. A channel at YouTube's own cap returns422 upload_limit_exceeded.
Those two errors are kept apart on purpose. A project-wide quota stop and one channel hitting its cap need different handling, and one busy channel shouldn't pause uploads for everyone else.
The same posting API publishes to Instagram, TikTok, Facebook, X, and Bluesky. A YouTube channel counts as one connected account, and your first 3 connected accounts are free.