YouTube Uploader Pro - Shorts avatar

YouTube Uploader Pro - Shorts

Pricing

from $550.00 / 1,000 short uploadeds

Go to Apify Store
YouTube Uploader Pro - Shorts

YouTube Uploader Pro - Shorts

Publish a vertical video to your own YouTube channel as a Short, from a URL. You bring your own session cookies, so your login stays yours. Reads the frame size and duration from the first megabyte where the host allows it, so most bad files are refused before the rest transfers.

Pricing

from $550.00 / 1,000 short uploadeds

Rating

0.0

(0)

Developer

Dami's Studio

Dami's Studio

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

Share

YouTube Uploader Pro — Shorts

Publishes a vertical clip to your own YouTube channel as a Short.

You give it a direct link to an mp4 and a title. It opens YouTube Studio in a real browser, signs in with your cookies, fills in the upload wizard, and hands you back the video id and the Shorts URL.

Whether YouTube files something as a Short is decided by the file, not by you. The frame has to be square or taller, and the clip has to be 180 seconds or less. There is no toggle for it, and #Shorts in the title changes nothing. So this Actor reads your file before it transfers it, and stops early if the file would not qualify. One kind of file cannot be read that way, and for that one the decision lands after the transfer — there is a section below on which.

What you need

  • A public direct link to the .mp4. It has to serve the file itself, not a page with a player on it. S3, a CDN, or an Apify key-value store record all work.
  • A title.
  • Your YouTube cookies, pasted once. Instructions below.

Costs

You are billed for the run start, for every megabyte of video the run transfers, and for the publish once YouTube confirms it. Megabytes are counted on every attempt, including a failed upload and a transfer that had to restart, so a run can bill more megabytes than the file holds. The prices are listed on this Actor's store page.

Read this first

This drives the YouTube Studio web interface with your session cookies. That goes against YouTube's Terms of Service, and if YouTube objects it is your channel that pays for it, not this Actor. Use it on channels you own and are willing to risk.

The official Data API is not a way around that. Videos uploaded through videos.insert from an unverified API project created after 28 July 2020 are locked to private forever, and YouTube's own help pages say there is no appeal — you delete and re-upload. That is why there is no API mode here.

Getting your cookies in

The Actor signs in as you, using the cookies from a browser where you are already signed in. You copy them out once and paste them into the input.

  1. Install Cookie-Editor in Chrome. Chrome Web Store.

    Chrome is the browser these steps were written against and the only one they were tested in. Step 2 turns on a permission from chrome://extensions, a page Firefox does not have. Cookie-Editor has a Firefox build and its JSON export is the same shape, so it may well work — but nobody here has run it, so treat a Firefox export as untested.

  2. Give Cookie-Editor access to every site. Open chrome://extensions, find Cookie-Editor, click Details, and set site access to "On all sites". Do this before you export anything.

    Without it, SID, HSID and APISID are missing from the export. The export looks complete either way, which is what makes this the step people lose an hour to.

  3. Sign in to YouTube in that browser, on the account that owns the channel you want to upload to. If the account has several channels, switch to the right one now.

  4. Go to https://www.youtube.com/robots.txt, open Cookie-Editor and choose Export → JSON. That copies the cookies to your clipboard. Nothing visible happens, which is normal.

    robots.txt rather than the YouTube home page because it is a plain text file with nothing loading behind it. A normal YouTube page keeps making requests while you sit on it, and one of those can re-mint a cookie between your export and your paste.

  5. Paste it into the box labelled "YouTube session cookies — paste both exports, then leave empty".

  6. Now go to myaccount.google.com and do Export → JSON again. Paste that second export into the same box, straight after the first one. Leave the first one where it is. Two blocks of JSON in one field is what the Actor expects, and it sorts them out itself.

    The reason you do it twice: the extension can only ever copy the cookies for the site you are currently on, and the upload passes through both youtube.com and accounts.google.com. This is measured, not theoretical. An export from youtube.com on its own gets a sign-in page.

  7. Start the run within a few minutes. One pair in the session, __Secure-1PSIDTS and __Secure-3PSIDTS, is re-minted about every ten minutes. An export that has sat in a text file for an hour carries a pair Google replaced several times over.

  8. After the first run that works, empty the cookie field and leave it empty. The Actor keeps the session and refreshes it every time it runs. Paste again only when you want to upload to a different account, or when a run tells you the session stopped working.

When Google says "Verify it's you"

Sometimes Google shows a verification screen instead of Studio. The run stops there with a SESSION_CHECKPOINT code and uploads nothing. There is no way for the Actor to answer that screen for you.

Clear it in your own browser: open YouTube on that account, do whatever Google asks for — a code, a tap on your phone — and get back to a normal signed-in page. Then export your cookies again and start a new run. Re-exporting on its own does not help if you have not cleared the challenge first; the session stays flagged.

Matching the proxy country to where you exported your cookies is worth doing, but do not count on it: a run through an exit in the account's own country met this same challenge. What actually sets it off is not something this Actor can see.

Where your session is kept

Once a run signs in and reads back which channel it landed on, it saves that session so you do not have to paste again. Both halves are needed. A run that signs in but cannot get Studio to name the channel saves nothing, leaves whatever is already stored untouched, and says so in the log — cookies a run cannot attribute are how a session for the wrong account becomes permanent.

There are two records: the refreshed cookie jar, and a compressed copy of the browser profile directory. Both live in a key-value store called youtube-uploader-sessions in your own Apify account — the account that runs the Actor. Nothing is sent to the developer, and no cookie value ever goes into the run's own output store or into a dataset row. Together those two records are a working signed-in session for that Google account, so treat them the way you treat the cookie export itself.

They do not expire. Three things clear them:

  • Tick Forget the saved session (resetProfile) and paste a fresh cookie export in the same run. Both records are deleted before anything reads them, and rebuilt from what you pasted. Ticking it without pasting cookies does nothing at all, on purpose: a run with no session to sign in with should not be able to destroy your saved one.
  • Delete the store yourself in Apify Console → Storage → Key-value stores → youtube-uploader-sessions. The cookie jar is named session-<channelId> and the profile archive profile-<channelId>.tar.gz, with default in place of the id when you have not set a channelId.
  • A run that lands on a channel other than the one it asked for clears the records it read or wrote on that run, so a session that publishes to the wrong place cannot settle in. Records it neither read nor wrote are left alone. The stored profile is also dropped when Chrome starts on it and then goes away, and the run falls back to the cookie jar once.

To switch accounts you only need to paste. Anything you paste wins outright, and the saved session is not consulted on that run.

Deleting a record does not sign the Google account out. For that, use Google's own Security → Your devices → Sign out, which kills every copy of that session including any you exported.

Try it without signing in

Run it with no cookies on an account that has no saved session yet, or tick demoMode, and you get one clearly labelled sample row. It uploads nothing and moves no video bytes. The run start is still billed. Use it to see the output shape first.

Once a session is saved, an empty cookie field means a real upload. Two things reach the sample row after that: demoMode, or Forget the saved session ticked with the cookie field still empty.

What it refuses

The run finishes as succeeded and writes one row carrying the code below, a plain-English error saying what to change, and a hint. Nothing is published.

CodeWhat happened
SHORTS_TOO_LONGLonger than 180 seconds. Past that it stays long-form however you tag it.
SHORTS_NOT_VERTICALThe frame is landscape. A landscape file never becomes a Short at any length.
SHORTS_SQUARE_SOURCEExactly 1:1. It does become a Short, but with about 40% of the frame as black bars. Tick Allow square (1:1) source if you meant it.
SHORTS_ROTATION_METADATAStored landscape and only looks vertical because of a rotation flag. YouTube reads the stored frame, so this publishes as a normal video without warning.
SHORTS_RESOLUTION_TOO_HIGHShort edge above 1080 px, which is YouTube's stated Shorts maximum. No viewer receives those extra pixels. Tick Allow above-target resolution to send it anyway.
SHORTS_COPYRIGHT_CLIFFOver 60 seconds and you ticked Contains third-party audio. Past a minute, any active claim blocks the Short worldwide, after it is already published.
UNPROBEABLE_CONTAINERNot an MP4 or MOV, so the duration and frame size cannot be read — and those two numbers are the whole Shorts decision. WebM, Matroska, AVI, FLV, Ogg, WMV and the MPEG streams all land here, and all of them are refused on the first megabyte of the file. Re-encode to MP4/H.264/AAC.
VIDEO_TOO_SMALLUnder 2 KB, which YouTube answers with "Rejected (file too small)". Check that your URL points at the video and not at an error page — an expired signed URL returns one with HTTP 200.
NO_VIDEO_TRACKAudio-only file. YouTube cannot make a video out of an MP3.
OVERSIZED_VIDEOThree things fire this and the message says which. (1) The file is above the 512 MB ceiling — compress it or split it. (2) It fits the ceiling, but this run's Maximum total charge (USD) (maxTotalChargeUsd) does not leave room for it — raise the budget or send a smaller file; nothing is wrong with the video. (3) The run could not establish what it is allowed to charge for those megabytes, so it declined to move them; the file and your settings are both fine, so send something smaller or try again later. The declared content-length is checked first, so an oversized file is normally refused without being downloaded.
WRONG_CHANNELThe session landed on a channel other than the channelId you asked for. Studio does that silently when the account does not manage the channel, so the run stops instead of publishing to the wrong place.
VISIBILITY_UNCONFIRMEDThe visibility setting could not be read back off the page, so the run refused to press Publish and saved a draft. Clicking a radio button and having it selected are two different things, and the gap between them is a private video going public.
BAD_INPUTSomething in the input stops the run before it does any work. A missing or non-https videoUrl, a missing title, a channelId that is not a UC… id, cookies that could not be read, or a Maximum total charge (USD) too low to cover the publish fee.

Two things get a warning but not a refusal: an MP4 whose moov atom sits at the end of the file (a common cause of "Processing abandoned" — fix it with ffmpeg -i in.mp4 -c copy -movflags +faststart out.mp4), and a Short over 60 seconds with third-party audio left unticked.

Titles over 100 characters and descriptions over 5000 are cut to fit, and the log says by how much.

Session, run and Studio codes

These are the codes that are not about your file. They end the same way as the ones above: the run finishes as succeeded, writes one row, and publishes nothing.

CodeWhat happened
SESSION_INCOMPLETEThe pasted cookies do not add up to a Studio session. Check that both exports are in the box, and that Cookie-Editor had access to all sites before you took the first one.
SESSION_INVALIDA session cookie could not be used — either its own expiry had already passed, or none of them survived being loaded into the browser. Re-export both sets and start the run within a few minutes.
SESSION_EXPIREDGoogle served its sign-in page instead of Studio. Re-export both sets, including __Secure-1PSIDTS and __Secure-3PSIDTS, which rotate about every ten minutes. Do not keep browsing YouTube on that account afterwards — an open tab rotates the cookies you just exported.
SESSION_CHECKPOINTGoogle served a "verify it's you" challenge instead of Studio. There is a section on this above.
BROWSER_GATEThe run reached Google's sign-in form and was blocked there with "this browser or app may not be secure". Reaching a sign-in form at all means the session was not accepted as signed in, so the thing to re-export is the session.
UNSUPPORTED_CONTAINERThe file arrived intact and its opening bytes name a container this Actor does not upload. It is the rare sibling of UNPROBEABLE_CONTAINER: it only appears when the first-megabyte probe could not run at all, so the container was named once the file was already on disk.
VIDEO_UNAVAILABLEThe file could not be fetched. A bad status, a timeout, a content type that is not a video, zero bytes, or an HTML page where the video should be. An expired signed URL returns HTTP 200 with an error page, which is why the file's magic bytes are checked and not its content type.
DAILY_LIMITThe channel's 24-hour upload cap. There is a section on this below.
UPLOAD_REJECTEDYouTube refused the file after it arrived. The row quotes Studio's own sentence, which does not name what YouTube objected to.
UPLOAD_UNCONFIRMEDPublish was clicked and Studio never confirmed it. The Short may still have published — open Studio and look before you retry, because a blind retry can double-post.
LAYOUT_CHANGEDA step in Studio answered no selector this Actor knows. There is a section on this below.
NETWORKA network or proxy error stopped the run reaching YouTube. Retry, or change the proxy country.
TIMED_OUTThe run hit its time limit waiting on YouTube and stopped itself rather than being killed. Check the channel before retrying: if Publish had already been clicked, YouTube may still finish the upload.
INTERNALAn unexpected error inside the Actor. The run id and the saved screenshot are what get it fixed.

How much of your file moves before a refusal

Every megabyte the run transfers is billed, including on a run that ends in one of the codes above, so it is worth knowing which refusals land before the file moves.

Most of the checks above run on a Range request for the first megabyte of your URL, so a landscape clip is caught for a megabyte rather than five hundred. If that megabyte does not carry what the checks need, one more is read from the end of the file. The probe reads 2 MB at most and never reads past it.

A container that is not MP4 or MOV is named from that first megabyte and refused there, with UNPROBEABLE_CONTAINER. WebM, Matroska, AVI, FLV, Ogg, WMV and the MPEG streams each carry a fixed signature in their opening bytes, and no later byte can turn one of them into an MP4, so the rest of the file never moves. That holds even when your host ignores the Range header and starts sending the whole file: the probe stops reading at a megabyte either way.

One kind of file still falls through: a valid MP4 or MOV whose moov atom sits at the end, on a host that ignores Range and sends the whole file from byte zero. The first megabyte holds no moov, and the request for the last megabyte comes back as the first one again. So that file is downloaded in full, read off disk, and judged then, and a SHORTS_* refusal can arrive after the whole file has crossed.

Serve a faststart MP4 from a host that answers a ranged request with HTTP 206 and the decision stays on the first megabyte:

ffmpeg -i in.mp4 -c copy -movflags +faststart out.mp4

The transfer is bounded whatever the file is: a content-length declared above the 512 MB ceiling is refused before a byte moves, bytes are counted as they arrive so a server that declares no length is still cut off part-way once it passes the ceiling, and retries after a broken transfer count against one running total for the whole run instead of starting again from zero.

Input

FieldRequiredNotes
videoUrlfor real uploadsPublic https direct link to the .mp4. You host it.
cookiesfirst run onlyBoth exports, in the one field. See above.
titlefor real uploadsCut to 100 characters, loudly. A blank one is refused with BAD_INPUT: Studio pre-fills the title box with your filename, so a blank title would publish a Short called video.mp4.
descriptionnoCut to 5000 characters, loudly.
tagsnoJoined and trimmed to YouTube's 500-character tag field. A minor signal on Shorts — the title and the first few frames do far more.
visibilitynopublic, unlisted or private. Defaults to private.
madeForKidsnoThe COPPA audience declaration. Binding on you, not on this Actor.
channelIdnoThe UC… id, not an @handle and not a Brand-account id. Use it when the account manages more than one channel: the run stops with WRONG_CHANNEL rather than publishing to the wrong one.
thumbnailUrlnoBest-effort. Public link to a JPG or PNG. Custom thumbnails need a phone-verified channel; on an unverified one Studio takes the click and does nothing, and the row says so.
allowSquarenoUploads a 1:1 source instead of refusing it. It does become a Short, with roughly 40% of the frame as black bars.
allowAboveTargetResolutionnoUploads a source whose short edge is above 1080 px. No viewer receives those pixels.
containsThirdPartyAudionoTick it when the music or audio is not yours. It turns a Short over 60 seconds into a refusal instead of a warning, because past a minute an active claim blocks the Short worldwide after it is already published.
verifyShortsClassificationnoOn by default. After publishing, polls the watch URL for up to 45 seconds to see whether YouTube filed it under /shorts/. An unresolved poll reports null.
resetProfilenoDeletes the saved session before anything reads it. Only does anything when you paste a fresh export in the same run.
proxyConfigurationnoThe group you pick is ignored — this Actor picks its own datacenter lane in code. What does take effect is the country (match where you signed in) and your own servers in proxyUrls.
demoModenoForce a no-upload sample row even with a session available.

maxTotalChargeUsd sits in the run options rather than the input form. A cap set below what the run needs stops it rather than shrinking the bill: too low for the publish fee ends the run with BAD_INPUT before a browser opens, and too low for your file's megabytes ends it with OVERSIZED_VIDEO after the probe and before the transfer.

Example input

{
"videoUrl": "https://cdn.example.com/clip-42.mp4",
"title": "The one-take pancake flip",
"description": "Filmed on a Tuesday.\n\n#cooking",
"tags": ["cooking", "kitchen", "breakfast"],
"visibility": "private",
"channelId": "UCXXXXXXXXXXXXXXXXXXXXXX"
}

Output

One dataset row per run. A confirmed publish looks like this:

{
"ok": true,
"billable": true,
"format": "short",
"published": true,
"confirmed": true,
"videoId": "dQw4w9WgXcQ",
"shortsUrl": "https://www.youtube.com/shorts/dQw4w9WgXcQ",
"watchUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"visibility": "public",
"visibilityConfirmed": true,
"classifiedAsShort": true,
"durationSec": 42.5,
"width": 1080,
"height": 1920,
"videoMegabytes": 25,
"sessionRefreshed": true,
"warnings": []
}

The row also carries platform, description, tags, madeForKids, channelId, thumbnailApplied, draftSaved, checksState, studioEditUrl, videoBytes, egress, note, publishedAt and runId.

classifiedAsShort is either true or null, never false. Classification is asynchronous and YouTube publishes no timing guarantee, so a poll that did not resolve in 45 seconds is an unknown rather than a failure.

thumbnailApplied: false on an otherwise good row means Studio ignored your thumbnail. That happens on channels that are not phone-verified.

A watch page showing only 360p right after the run is normal. Higher resolutions fill in over the following hours.

A run that did not publish still finishes as succeeded and writes one row with a stable errorCode, a plain-English error, and a hint. Every code this Actor can raise is in the two tables under "What it refuses"; SESSION_CHECKPOINT, LAYOUT_CHANGED and DAILY_LIMIT also have sections of their own.

The result row is written before the charges are made, so a failed row cannot say what the run cost. It carries charged: null for that reason, and uploadFeeCharged: false, which is the part it does know. What was actually charged is written afterwards to the run's key-value store under CHARGE_RESULT.

What lands in the run's key-value store

When a step fails, a screenshot and a copy of the page HTML go in so you can see what the browser saw. They are named after the step that stuck, or yt-error when nothing more specific was reached.

The HTML is scrubbed before it is stored. Script bodies, cookie names and token-shaped strings are replaced with placeholders, because a signed-in Studio page carries live auth material and this store is a customer-facing output. Read it for page structure and visible text, and do not conclude anything from what is missing. A page that could not be scrubbed is not stored at all, and the log says so.

The screenshot is not scrubbed and cannot be. It is a picture of a signed-in Studio page, so it shows the account name and the avatar. That is deliberate — the rendered page is the half that shows you which step stuck — and it is your own account in your own store. Treat it the way you would treat a screenshot of your own browser, and think about that before you hand anyone the store or its id.

No cookie value is ever written here. The session lives in youtube-uploader-sessions, which is a different store.

When it breaks

This drives the real Studio interface, so a YouTube layout change can invalidate a selector. When that happens the run stops with LAYOUT_CHANGED, naming the step it was on, and saves the screenshot and the scrubbed HTML. It never reports a success it did not see. Whether it was a redesign, a page that was still rendering, or something the account ran into is not something the run can tell apart from that side — send the run id along with the screenshot and the selectors get checked. If it died mid-wizard it clicks Save and close first, so you are left with one recoverable draft rather than an abandoned partial upload.

The other code worth knowing is DAILY_LIMIT. It is a per-channel 24-hour cap and YouTube has never published a number for it, so the run cannot tell you how many uploads you have left or when the window reopens. Retrying inside the window has not been observed to clear it. Wait a day. Phone-verifying the channel is the documented lever on the cap.

Notes

  • Run one upload at a time per account. Two runs on the same session look like a hijack to Google.
  • The video is pulled from your URL directly, not through the proxy. What does go through it is the browser's own traffic: the Studio session, including the push up to YouTube.
  • Studio's "altered or synthetic content" question is answered No. If your Shorts need that disclosure, set it by hand in Studio afterwards.