YouTube Uploader Pro
Upload a vertical video to your own YouTube channel as a Short, straight from a URL. You bring your own session cookies, so your login stays yours.
How it works
- 1Open it on Apify
Hit Run on Apify — it opens the tool in the cloud, no install.
- 2Set the inputs
Adjust
videoUrl,title,description(sensible defaults are pre-filled). - 3Click Run
The tool runs on Apify’s cloud and collects the data for you.
- 4Export the results
Download as JSON, CSV or Excel, or pipe straight into your app, Google Sheets, or an AI agent.
Pricing
$0.55 per short uploaded = $550 per 1,000
| You are charged for | When | Price |
|---|---|---|
| Short uploaded | Charged once per Short that YouTube confirms was published. Runs that publish nothing — missing or expired cookies, a rejected file, or any failure — are never charged this fee, but they are still charged for the megabytes they transferred, under "Video megabyte". | $0.55 |
| Video megabyte | Charged per megabyte, rounded up, for the video bytes a run actually pulls in from your URL, and charged on EVERY outcome — not only on a confirmed upload — because that transfer is paid for the moment it happens. Your file crosses the network twice on a full upload, in from your URL and out to YouTube, and is counted once. A run that transfers less than 64 KB is not charged, a run whose result row never reaches the dataset is not charged, and the total never goes past the "maxTotalChargeUsd" you set. | $0.02 |
Pay-per-event pricing: you are billed per result, not per subscription. Billing is handled by Apify on your own account. These are the live Apify store prices, in effect since 2026-08-25, and they are what you are actually charged.
Inputs
| Field | What it does | Type |
|---|---|---|
videoUrl | Public direct https:// URL to the vertical .mp4 to publish as a Short (S3, CDN, key-value store, anything that serves the file itself, not an HTML landing page). Required for a real upload. To see the sample row instead, run it with the cookie field blank on an account that has no saved session yet, or tick "demoMode". The file is checked first: it must be square or taller and 180 seconds or less, or YouTube will not file it as a Short. That check normally runs on the first megabyte, read over a range request before the transfer. A container the probe cannot read, or a host that answers a ranged request with the whole file, is checked after the transfer instead, see the README. | string |
title | The Short's title, capped at YouTube's 100 characters (a longer one is cut and the cut is reported in the log, never silently). Required for a real upload: YouTube pre-fills the title box with the video's filename, so a blank title publishes a Short called "video.mp4". You do not need #Shorts, classification is decided by aspect ratio and duration, not by the title. | string |
description | The Short's description, capped at YouTube's 5000 characters. Keep hashtags to 15 or fewer: past 60 hashtags across the title and description YouTube ignores all of them and can drop the video from search. | string |
tags | Optional keyword tags. YouTube caps the whole tag field at 500 characters, so the list is joined and trimmed to fit. Tags are a minor ranking signal on Shorts, the title and the first frames matter far more. | array |
visibility | Who can see the Short. Defaults to Private on purpose, so nothing is ever published publicly by accident. The actor reads the selected radio back off Studio before it presses Publish; if it cannot confirm the setting it saves a draft and refuses to publish rather than risk a private video going public. | string |
madeForKids | YouTube's COPPA audience declaration. Leave unticked for content not made for children. This is a legal declaration about your own content, the actor sets what you choose here and verifies the radio took. | boolean |
channelId | Optional. The UC... id of the channel to upload to (24 characters starting with UC, not an @handle, not a Brand-account id). Use it when the signed-in account manages several channels: Studio silently redirects to the account's default channel when it does not manage the one you asked for, and the actor stops with WRONG_CHANNEL rather than uploading to the wrong place. Leave blank to use the channel the session lands on. | string |
thumbnailUrl | Optional, best-effort. Public https:// URL to a JPG/PNG thumbnail. Custom thumbnails require a phone-verified channel; on an unverified channel Studio silently ignores the upload, and the result row reports thumbnailApplied: false instead of pretending it worked. | string |
allowSquare | A 1:1 video DOES become a Short. YouTube's criteria say "square or vertical", but it plays with roughly 40% of the Shorts frame as black bars. A square source is refused by default so that the black bars are something you chose rather than something you find on the published Short, and the refusal normally lands on the first megabyte, before the transfer. Tick this to upload it as-is. | boolean |
allowAboveTargetResolution | YouTube states "You can upload Short videos with a maximum resolution of 1080p". A source whose short edge is above 1080 px is refused by default, because every pixel above that is bandwidth you are billed for and quality no viewer receives. Tick this to upload it anyway. | boolean |
containsThirdPartyAudio | Tick this if the Short uses music or audio you did not create. Over 60 seconds, YouTube's rule is absolute: any Short with an active copyright claim of any type, including manual claims, is blocked GLOBALLY, after publishing. With this ticked, a Short longer than 60 seconds is refused, normally on the first megabyte and before the transfer. With it unticked, the run continues and logs the risk. | boolean |
verifyShortsClassification | After publishing, poll the watch URL for up to 45 seconds to see whether YouTube has redirected it to /shorts/. Classification happens asynchronously with no published timing guarantee, so an unresolved poll reports null rather than a failure. The video is published either way. | boolean |
cookies | PASTE THIS THE FIRST TIME, THEN LEAVE IT EMPTY. Anything you paste here WINS: the run signs in with it and does not read the saved session at all. Leave the field empty and the run uses the session saved by an earlier run instead. With nothing pasted AND no saved session, the run returns the labelled sample row and uploads nothing. Paste again whenever you want to switch Google account or recover, a paste always overrides what is stored, so a saved session can never publish to an account you did not just name. HOW TO EXPORT: paste TWO Cookie-Editor JSON exports here, one after the other (order does not matter, and you do not need to merge them yourself). (1) On https://www.youtube.com/robots.txt while signed in: Cookie-Editor > Export > JSON. robots.txt because it is a plain text page with nothing loading behind it, so no background request can re-mint a cookie between the export and the paste. (2) On https://myaccount.google.com/ in the same window: Export > JSON again. BOTH are needed. Cookie-Editor can only ever export the domain of the tab you are on, and the upload flow passes through accounts.google.com, so a youtube.com-only export arrives there with nothing to show and Google draws its sign-in page. Chrome is the tested path: before exporting, set the extension to "On all sites" in chrome://extensions > Cookie-Editor > Details, or it silently omits every non-Secure cookie (SID, HSID, APISID). Export and run within a few minutes: Google re-mints the rotating cookies about every 10 minutes, and an export is good for one use only, do not test the same jar elsewhere first. Close the window afterwards, but do NOT sign out; signing out kills the session for every copy. WHAT GETS SAVED: once a run authenticates AND reads back which channel it is signed into, this Actor stores that session, the refreshed cookie jar plus a gzipped copy of the Chrome profile directory (cookies, Local Storage, IndexedDB, service workers, device identifiers), in a key-value store named "youtube-uploader-sessions" in YOUR OWN Apify account, never the developer's and never in a run's output store. A run that signs in and cannot read the channel back saves nothing and says so in the log. Later runs open Chrome on it already signed in. It does not expire; to delete it, tick "Forget the saved session" (resetProfile) together with a fresh export, or delete that store in Apify Console > Storage, which does not sign the Google account out. | string |
resetProfile | A RECOVERY HATCH, not the way to switch accounts. To switch accounts you only need to paste the new account's export, anything you paste already wins over the saved session, which is never consulted on a run that supplies cookies. Tick this box only when the SAVED session itself has gone bad and you want it gone: the account was signed out everywhere, the password changed, or the profile was written by a Chrome build this image cannot open. It deletes BOTH saved records for this channel, the browser profile and the rolling cookie jar, before the run reads anything. YOU MUST PASTE A FRESH COOKIE EXPORT IN THE SAME RUN. Ticking this on its own does nothing at all: that run would have no session to sign in with, so it returns the sample row and deletes nothing, a run that never opens a browser must not be able to destroy your saved session. Untick the box and empty the cookies field again afterwards. | boolean |
SID | In your browser: DevTools > Application > Cookies > https://www.youtube.com, find the row named SID and copy its Value column. Paste just the value. It must come from the .youtube.com list, NOT the .google.com one. Studio never receives google.com cookies. | string |
HSID | From the same .youtube.com Cookies list. This one is HttpOnly, so a JavaScript/console cookie dump will not contain it, you need a cookie-manager extension (Cookie-Editor / Cookie Quick Manager). | string |
SSID | From the same .youtube.com Cookies list. HttpOnly, like HSID, so a console cookie dump will not contain it either. | string |
APISID | From the same .youtube.com Cookies list. Paste just the value. | string |
SAPISID | Load-bearing beyond page load: Studio's own JavaScript derives the `Authorization: SAPISIDHASH` header from this value, and every Studio request 401s without it even though the page renders. | string |
__Secure-1PSID | The first-party session id. HttpOnly. | string |
__Secure-3PSID | The third-party-context session id. Studio specifically needs this one, an export filtered to first-party cookies only produces a session that looks complete and still bounces to the sign-in page. | string |
LOGIN_INFO | YouTube's own logged-in-identity cookie. Studio bounces to a sign-in without it even when the whole SID family is intact. It is also the cheapest single proof that your export came from a real cookie API rather than a console dump, it is HttpOnly, so a `document.cookie` paste can never contain it. | string |
__Secure-1PSIDTS | The rotating freshness partner of __Secure-1PSID (TS = timestamp). Google re-mints it roughly every 10 minutes. Its absence makes Google treat the session id as unbound, i.e. stolen-looking, and redirect to sign-in. Export it fresh, minutes before the run. | string |
__Secure-3PSIDTS | The same, for __Secure-3PSID. Google re-mints it roughly every 10 minutes, and a session id with no matching PSIDTS is treated as unbound. Export it fresh, minutes before the run. | string |
__Secure-1PAPISID | Backs the SAPISID1PHASH Authorization scheme. | string |
__Secure-3PAPISID | Backs the SAPISID3PHASH Authorization scheme. YouTube falls back to this when SAPISID is missing. | string |
demoMode | Force a labelled SAMPLE run that uploads nothing and moves no video bytes, even with a session available. A run with no cookies pasted AND no saved session is a sample run too. Once a session is saved, two things reach the sample row: this box, or "Forget the saved session" ticked with the cookie box left empty. | boolean |
What you get
A structured dataset — each result includes fields like:
okvideoIdshortsUrltitlevisibilityvisibilityConfirmedclassifiedAsShortvideoMegabytespublishedAterrorCodeerrorExport every run as JSON, CSV or Excel, or send it to your app, a database, Google Sheets, or an AI agent.
Related tools in Developer & Research Tools
Other ready-to-run tools in the same category — all pay-per-use on the Apify cloud.
GitHub Scraper
Search GitHub repos and users: stars, forks, language, topics, licence, plus user bio, company and followers. No token needed. $0.90 per 1,000 rows.
Stack Overflow / Stack Exchange Scraper
Search Stack Overflow and Stack Exchange by keyword or tag. Score, answer count, views, reputation and body text. $2 per 1,000 questions.
Package Registry Scraper (npm + PyPI)
Get npm and PyPI package metadata as JSON. Version, license, author, repo, keywords and npm monthly downloads. $2 per 1,000 packages.
arXiv Scraper
Search arXiv papers by title, author, abstract or category. Get full abstracts, authors, categories, DOI, dates and PDF links. $2 per 1,000 papers.
OpenAlex Scholarly Works Scraper
Search 250M+ OpenAlex papers with no API key. Get titles, authors, venue, year, citations, DOI, OA links and full abstracts. $2.00 per 1,000 papers.
Crossref Scholarly Works Scraper
Search 150M+ papers on Crossref: DOI, title, authors, journal, publisher, date, citations and abstract. No API key. $1.00 per 1,000 works.
Where this tool sits
- Categories
- Developer & Research Tools
YouTube Uploader Pro for Shorts: publish a vertical MP4 to your own channel from a URL
Give it a direct link to a vertical .mp4 and a title. It opens YouTube Studio in a real browser, signs in with your own session, fills in the upload wizard and hands back the video id and the Shorts URL.
Two things to know before you spend anything. It signs in as you, using cookies you export from your own browser, so the first run takes ten minutes of setup and the risk sits with your channel: driving Studio this way is against YouTube's terms. And whether YouTube files a clip as a Short is decided by the file, not by a tag. Square or taller, 180 seconds or less. #Shorts in the title changes nothing, and the decision cannot be undone after publishing, so the file is checked before it moves.
| Input | A public direct link to a vertical .mp4, a title, and your own YouTube session |
| Output | One row per run, with the video id and the Shorts URL |
| Ceiling | 512 MB per file, 180 seconds, 1080 px short edge |
| Account needed | Your own YouTube channel, signed in with your own cookies |
| Price | $0.55 per published Short, plus $0.02 for each megabyte transferred |
🎬 What YouTube Uploader Pro for Shorts does
It reads your file before it transfers it. Frame size, duration and container come off the first megabyte, so a landscape clip is turned away for a megabyte rather than five hundred. Three of those checks are yours to waive: a 1:1 source becomes a Short with roughly 40% of the frame as black bars, a short edge above 1080 px is quality nobody receives, and a Short over 60 seconds carrying somebody else's audio can be blocked worldwide by a claim after it is live. Each is refused by default, each has a box to tick.
Visibility defaults to private on purpose, and the run reads the radio button back off Studio before it presses Publish. If it cannot confirm the setting it saves a draft and refuses to publish, because clicking a radio and having it selected are two different things.
📥 What you give it
{
"videoUrl": "https://cdn.example.com/clip.mp4",
"title": "The 64 sugar cubes puzzle",
"visibility": "private"
}
| Field | Default | What it is |
|---|---|---|
videoUrl | none | A public https:// link to the file itself, not a page with a player on it. You host it. |
title | none | Cut to YouTube's 100 characters, and the log says by how much. A blank one is refused: Studio pre-fills the box with your filename, so blank would publish a Short called video.mp4. |
description | none | Cut to 5,000 characters. Keep hashtags to 15 or fewer. |
tags | [] | Optional. A minor signal on Shorts next to the title and the first few frames. |
visibility | private | public, unlisted or private. |
madeForKids | false | YouTube's COPPA audience declaration. It binds you, not this actor. |
channelId | none | The UC... id, not an @handle. Use it when the account runs several channels: Studio redirects silently, so the run stops with WRONG_CHANNEL instead of publishing to the wrong one. |
thumbnailUrl | none | Best effort. Custom thumbnails need a phone-verified channel; on an unverified one the row reports thumbnailApplied: false rather than pretending. |
allowSquare | false | Upload a 1:1 source instead of refusing it. |
allowAboveTargetResolution | false | Upload a source whose short edge is above 1080 px. |
containsThirdPartyAudio | false | Tick it when the audio is not yours. It turns a Short over 60 seconds into a refusal rather than a warning. |
verifyShortsClassification | true | After publishing, watches for up to 45 seconds to see whether YouTube filed it under /shorts/. |
cookies | none | Your session, pasted once. See below. Marked secret. |
resetProfile | false | Deletes the saved session before anything reads it. Only does anything when you paste a fresh export in the same run. |
proxyConfiguration | Apify network on | The country is worth matching to where you exported. Your own servers in proxyUrls are used as given. Your video file is always fetched straight from your URL. |
demoMode | false | Forces the labelled sample row. Nothing is uploaded and no video bytes move. |
The twelve individual cookie fields underneath are a fallback. Anything in the cookies box wins.
📤 What you get back
One row per run, whatever happens. A published Short carries these:
| Field | What it is |
|---|---|
videoId, watchUrl, shortsUrl, studioEditUrl | Where the Short now lives |
visibilityConfirmed, madeForKids, thumbnailApplied, tagsApplied | What was confirmed on the page, rather than what you asked for |
durationSec, width, height, videoBytes, videoMegabytes | What was read off your file |
classifiedAsShort, classificationNote | Whether YouTube filed it under /shorts/. true or null, never false |
warnings | Things worth knowing that did not stop the run |
A refusal writes one row too. This is a real one from a real run, with the long message cut short:
{
"ok": false,
"charged": null,
"uploadFeeCharged": false,
"errorCode": "SESSION_INCOMPLETE",
"error": "SESSION_INCOMPLETE: the cookies you supplied do not add up to a Studio session. ...",
"reason": "The cookies supplied do not add up to a YouTube Studio session. Nothing was uploaded, so no upload fee was charged ...",
"hint": "Export the WHOLE youtube.com cookie list, not a few fields. ...",
"platform": "youtube",
"posted": false,
"confirmed": false,
"videoId": null,
"watchUrl": null,
"finalUrl": null,
"diagnosedAt": "2026-08-24T23:31:23.917Z",
"title": "x",
"videoUrl": "https://httpbin.org/status/403",
"format": "short"
}
🧾 Reading the output
| Row | How to spot it | What happened |
|---|---|---|
| A published Short | ok: true with a videoId and a shortsUrl | It is on your channel |
| The sample row | _sample and _demo, ids reading SAMPLE00000 | Nothing was uploaded |
| A refusal | ok: false with an errorCode, reason and hint | Nothing was published |
The run itself still finishes as succeeded on a refusal, so read ok and errorCode rather than the run status. These are the codes about your file:
| Code | What happened |
|---|---|
SHORTS_TOO_LONG | Over 180 seconds. Past that it stays long-form however you tag it. |
SHORTS_NOT_VERTICAL | Landscape. It never becomes a Short at any length. |
SHORTS_SQUARE_SOURCE | Exactly 1:1. Tick Allow square (1:1) source if you meant it. |
SHORTS_ROTATION_METADATA | Stored landscape, vertical only by a rotation flag. YouTube reads the stored frame. |
SHORTS_RESOLUTION_TOO_HIGH | Short edge above 1080 px. Tick Allow above-target resolution to send it. |
SHORTS_COPYRIGHT_CLIFF | Over 60 seconds with Contains third-party audio ticked. |
UNPROBEABLE_CONTAINER | Not MP4 or MOV, so the duration and frame cannot be read. WebM, Matroska, AVI, FLV, Ogg, WMV and the MPEG streams all land here. Re-encode. |
UNSUPPORTED_CONTAINER | The same verdict reached after the file arrived, when the first-megabyte probe could not run. |
VIDEO_TOO_SMALL, NO_VIDEO_TRACK | Under 2 KB, or audio with no picture. |
VIDEO_UNAVAILABLE | The file could not be fetched: a bad status, a timeout, zero bytes, or an HTML page where the video should be. |
OVERSIZED_VIDEO | Above the 512 MB ceiling, or this run's Maximum total charge (USD) does not cover it. The message says which. |
BAD_INPUT | A missing or non-https videoUrl, a missing title, a channelId that is not a UC... id, or unreadable cookies. |
And these are about your session or Studio:
| Code | What happened |
|---|---|
SESSION_INCOMPLETE | The pasted cookies do not add up to a Studio session. |
SESSION_INVALID, SESSION_EXPIRED | The cookies were not accepted, or Google served its sign-in page. Re-export both sets and run within a few minutes. |
SESSION_CHECKPOINT | Google served a "verify it's you" challenge. Clear it in your own browser, then re-export. |
BROWSER_GATE | Google's sign-in form refused the browser. The session was not accepted, so re-export that. |
WRONG_CHANNEL | The session landed on a channel other than the channelId you asked for. |
VISIBILITY_UNCONFIRMED | The visibility could not be read back, so a draft was saved instead of publishing. |
DAILY_LIMIT | The channel's own 24-hour upload cap. |
UPLOAD_REJECTED | YouTube refused the file after it arrived, quoting its own sentence. |
UPLOAD_UNCONFIRMED | Publish was clicked and Studio never confirmed. Look in Studio before retrying: a blind retry can double-post. |
LAYOUT_CHANGED | A step in Studio answered no selector this actor knows. Tell us. |
NETWORK, TIMED_OUT, INTERNAL | The run could not reach YouTube, ran out of time, or hit something unexpected. |
▶️ How to run it
1. Open YouTube Uploader Pro for Shorts and put your .mp4 where it is served as a file: S3, a CDN, an Apify key-value store record. 2. Export your cookies, following the four rows below exactly. This is the step people lose an hour to. 3. Paste both exports into YouTube session cookies, fill in Title, set Visibility, and start within a few minutes. 4. Read the row: a Shorts URL on success, an errorCode and a hint on a refusal. 5. After that leave the cookie box empty. The session is saved and refreshed on every run. Paste again only to switch account, or when a run says the session stopped working.
| Step | What to do | Why |
|---|---|---|
| 1 | Install Cookie-Editor in Chrome, open chrome://extensions, Details, set access to On all sites | Without it the export silently omits SID, HSID and APISID, and still looks complete |
| 2 | Sign in to the right channel, go to https://www.youtube.com/robots.txt, Export to JSON | A plain text page cannot re-mint a cookie between your export and your paste |
| 3 | Go to https://myaccount.google.com/ in the same window, Export to JSON again | The extension only ever exports the tab's own domain, and the upload passes through both |
| 4 | Paste both blocks in the one box and start within a few minutes | Two cookies in the jar are re-minted about every ten minutes |
💰 How much does it cost?
$0.55 per published Short, plus $0.02 for each megabyte of video the run transfers.
Megabytes are counted on every attempt, so a transfer that breaks and restarts can bill more megabytes than the file holds. The publish fee is only charged once YouTube confirms the upload.
A landscape clip, a WebM or a file over 180 seconds is turned away on that first megabyte. No video bytes move on the sample row, or on a refusal that lands there, so neither carries megabytes or the publish fee.
💡 What people use it for
- Posting the vertical cut of something you already made, without opening Studio.
- Ending an editing pipeline with the upload itself, rather than a file in a bucket.
- Publishing to a schedule of your own from Apify, with visibility left private until you look.
- Posting to a second channel you manage, named by
channelIdso it cannot land on the wrong one.
🚧 What it does not do
- It cannot answer Google's "verify it's you" screen. That run stops at
SESSION_CHECKPOINT and publishes nothing. Clear the challenge in your own browser, then re-export.
- No scheduling, no category, no playlist. Those are deliberately left out here.
- MP4 and MOV only. WebM included in the refusals.
- One file per run.
- A rotation-flag vertical is refused, because YouTube reads the stored frame and would publish
it as a normal video with no warning.
classifiedAsShortresolves within 45 seconds or reportsnull, since YouTube decides that
on its own clock. The video is live either way.
- One shape of file is judged only after the whole transfer: an MP4 whose
moovatom sits at
the end, served by a host that ignores a range request. A SHORTS_* refusal then arrives with the megabytes already billed. Serve a faststart MP4 instead: ffmpeg -i in.mp4 -c copy -movflags +faststart out.mp4.
- Your saved session lives in a key-value store called
youtube-uploader-sessions**in your own
Apify account**. No cookie value ever reaches a dataset row or the run's output store.
🧭 Which YouTube actor do you need?
| If you want | Use |
|---|---|
| To publish a vertical clip as a Short | This one |
| To publish long-form, with category, playlist and scheduling | YouTube Uploader Pro for Videos |
| A plainer upload from a URL | YouTube Video Uploader |
| To download a video, its audio or its subtitles | YouTube Downloader Pro |
| The Shorts a channel has already published | YouTube Shorts Scraper |
❓ Questions people ask
Can I use the official API instead? Not usefully. A video uploaded through videos.insert from an unverified API project created after 28 July 2020 is locked to private forever, with no appeal on YouTube's own help pages. That is why there is no API mode here.
Where do my cookies go? Into your own Apify account, in the store named above, and into the browser this actor drives. Never to us.
Can I try it before pasting a session? Yes. Tick demoMode, or run it with no cookies on an account that has no saved session. You get one labelled sample row, no upload and no video bytes.
How do I switch channels? Paste the new account's export. Anything you paste wins outright and the saved session is not consulted on that run. Forget the saved session is a recovery hatch, not the way to switch.
Does deleting the saved session sign me out of Google? No. For that, sign the device out from Google's own Security page, which kills every copy of that session.
🆘 If something breaks
Open the Issues tab on the actor page with the run id and the row's errorCode. A LAYOUT_CHANGED row means Studio moved something and we need to hear about it.