YouTube Uploader Pro - Videos avatar

YouTube Uploader Pro - Videos

Pricing

from $550.00 / 1,000 video uploadeds

Go to Apify Store
YouTube Uploader Pro - Videos

YouTube Uploader Pro - Videos

Publish a long-form video to your own YouTube channel from a URL: title, description, tags, category, playlist, thumbnail, visibility and scheduling. You bring your own session cookies. It reads the file first where the host allows it, and refuses uploads YouTube would silently ruin.

Pricing

from $550.00 / 1,000 video 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 — Videos

Publishes a long-form video to your own YouTube channel from a URL.

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 wizard, waits for the bytes to finish moving, and reports back only what YouTube actually confirmed. Title, description, tags, category, playlist, custom thumbnail, made-for-kids, visibility, and scheduled publishing with a real timezone are all set for you.

This is the long-form edition. If your clip is vertical and under three minutes, use the Shorts edition instead — YouTube would classify it as a Short here anyway, and this Actor refuses that case rather than let it happen (see below).

What you need

  • A public direct link to the video file. 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 the refreshed cookie jar 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.

The jar lives 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. That record is a working signed-in session for your Google account, so treat it the way you treat the cookie export itself.

It does not expire. Three things clear it:

  • Tick Forget the saved session (resetProfile) and paste a fresh cookie export in the same run. The old record is deleted before anything reads it, 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 it yourself in Apify Console → Storage → Key-value stores → youtube-uploader-sessions. The record is named session-<channelId>, or session-default 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.

To switch accounts you only need to paste. Anything you paste wins outright, the saved jar is not consulted on that run, and the stored copy is replaced at the end of it.

Deleting the 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 before you wire up a session.

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

What it does, in order

  1. Checks your input with no network at all.
  2. Checks the cookie jar and fails fast on one that has already expired.
  3. Opens a headless Chromium through a datacenter exit and injects your session.
  4. Proves the session on Studio before downloading anything.
  5. Reads up to two megabytes of your video over range requests and applies every rule below, before the full transfer. One kind of file cannot be read that way, and for that one the checks move to step 6 instead.
  6. Downloads the file from its origin and re-checks the real bytes.
  7. Drives the wizard, reading back every control it touches — the title box, the audience radio, the visibility radio.
  8. Waits for the byte upload to finish, publishes, and confirms.

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
WOULD_BECOME_SHORTThe frame is square or taller and the clip is three minutes or under. YouTube files that as a Short, server-side, with no flag to turn it off and no way to undo it after publishing except deleting and re-uploading. You would have paid for a long-form upload and got a Short. Use a wider aspect ratio such as 16:9, or the Shorts edition of this Actor.
UNVERIFIED_CHANNEL_15MINOver 15 minutes with Channel is phone-verified left unticked. An unverified channel is capped at 15 minutes, and YouTube enforces it the expensive way: it accepts the whole file and rejects it afterwards with rejectionReason: "length". Verify at youtube.com/verify, then tick the box. One phone number verifies two channels a year.
VIDEO_TOO_LONGOver 12 hours. YouTube's hard ceiling is 256 GB or 12 hours, whichever comes first.
NO_VIDEO_TRACKAudio-only file. An MP3 or WAV cannot become a YouTube video. Render the audio onto a video track first.
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.
UNSUPPORTED_CONTAINERThe file's opening bytes name a container this Actor does not upload. It takes MP4/MOV and Matroska/WebM; AVI, FLV, Ogg, WMV and the MPEG streams are refused on the first megabyte, before the rest of the file moves. Re-encode to MP4 (H.264 video, AAC audio).
OVERSIZED_VIDEOThree things fire this and the message says which. (1) The file is above the 4000 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 redirects to the account's default channel without saying so when the account does not manage the one you named. There is no error to catch, so the run stops rather than publishing to the wrong place.
VISIBILITY_UNCONFIRMEDThe visibility setting could not be read back off the radio, so the run refused to press Publish. Clicking a radio button and having it selected are two different things, and the gap between them is a video that should have been private going public. The upload is saved as a draft in your Studio — open it and set visibility by hand, or run it again.
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, a Maximum total charge (USD) too low to cover the publish fee, or a publishAt that is in the past or carries no UTC offset. The offset is required because without one the schedule lands in whatever timezone Studio is showing, and YouTube renders published dates in Pacific Time on the watch page, so an ambiguous time reads back as the wrong day.

Warnings that do not stop the run: a title or description over the 100/5000-character limits (cut, and the log says by how much), more than 60 hashtags across the two (past that YouTube ignores all of them), and an MP4 whose moov atom sits at the end of the file rather than the front — a common cause of "Processing abandoned". Fix that one with ffmpeg -i in.mp4 -c copy -movflags +faststart out.mp4.

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.
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 video 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, before the rest of the file has moved. If that megabyte does not carry what the checks need, one more is read from the end. 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. AVI, FLV, Ogg, WMV and the MPEG streams are refused there with UNSUPPORTED_CONTAINER, and the rest of the file never moves — each carries a fixed signature in its opening bytes, and no later byte can turn one of them into an MP4. 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. Matroska and WebM are named the same way and then uploaded, because YouTube accepts them.

What a Matroska or WebM costs you is two checks. Neither the duration nor the frame size can be read out of that container, so WOULD_BECOME_SHORT and UNVERIFIED_CHANNEL_15MIN cannot run on it, and the log names both. A square-or-taller WebM of three minutes or less will publish as a Short you paid long-form pricing for, and one over 15 minutes on an unverified channel will be rejected by YouTube after every byte has moved. Send those two shapes as MP4.

One kind of file falls through the probe entirely: 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 WOULD_BECOME_SHORT or UNVERIFIED_CHANNEL_15MIN 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 4000 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.

After publishing: low resolution is normal

The video id and the watch URL exist right away, but YouTube processes resolutions in stages. The watch page may show only SD for a while, and 1080p or 4K fill in later. YouTube's own guidance is that a 60-minute 4K30 video can take up to four hours.

That is normal, not a failure. This Actor does not wait for it. Every successful row repeats it in the note field. The same goes for the first few minutes, when the watch page can read "unavailable" until the SD version lands.

Input

FieldRequiredNotes
videoUrlfor real uploadsPublic https direct link to the file. You host it.
cookiesfirst run onlyBoth exports, in the one field. Empty after that, once a session is saved. 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 video called video.mp4.
descriptionnoCut to 5000 characters, loudly.
tagsnoEntered in the advanced pane; the chips are read back afterwards.
visibilitynopublic, unlisted or private. Defaults to private.
publishAtnoRFC 3339 with an explicit offset, e.g. 2026-09-01T14:00:00-07:00. A scheduled video is private until its moment, so visibility is set to private for you and the log says what it becomes and when.
categoryIdnoPeople & Blogs (default), Entertainment, Comedy, Education.
playlistIdnoBest-effort. Studio's playlist picker is a search box, so a name that cannot be matched is reported rather than guessed at.
channelIdnoThe UC… id, not the @handle and not the Brand-account id.
thumbnailUrlnoBest-effort, under YouTube's own 2 MB limit. Custom thumbnails need a phone-verified channel; on an unverified one Studio takes the click and does nothing. The row reports whether it landed.
madeForKidsnoThe COPPA audience declaration. Binding on you, not on this Actor.
channelIsPhoneVerifiednoOpens up the over-15-minute path.
resetProfilenoDeletes the saved cookie jar 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 run even with cookies.

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. Long-form files make the second one easy to walk into.

Example input

{
"videoUrl": "https://cdn.example.com/episode-14.mp4",
"title": "Episode 14 - the long version",
"description": "Chapters in the comments.\n\n#podcast",
"tags": ["podcast", "interview", "long form"],
"categoryId": "22",
"visibility": "private",
"publishAt": "2026-09-01T14:00:00-07:00",
"channelIsPhoneVerified": true,
"channelId": "UCXXXXXXXXXXXXXXXXXXXXXX"
}

Output

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

{
"ok": true,
"billable": true,
"kind": "video",
"published": true,
"confirmed": true,
"videoId": "dQw4w9WgXcQ",
"watchUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"studioUrl": "https://studio.youtube.com/video/dQw4w9WgXcQ/edit",
"title": "Episode 14 - the long version",
"tags": ["podcast", "interview", "long form"],
"tagsApplied": true,
"categoryId": "22",
"visibility": "private",
"visibilityConfirmed": true,
"scheduled": true,
"publishAt": "2026-09-01T14:00:00-07:00",
"durationSec": 1847.2,
"width": 1920,
"height": 1080,
"videoBytes": 519045120,
"sessionRefreshed": true,
"warnings": []
}

The row also carries platform, playlistId, madeForKids, thumbnailApplied, alteredContentSet, draftSaved, checksState, sourceUrl with the query string masked, sessionRefreshNote, egress, note, publishedAt and runId.

scheduled and publishAt are false and null unless you asked for a schedule. warnings collects anything best-effort that did not land — a thumbnail Studio ignored, a playlist name it could not match, tags that did not read back.

If Studio confirms the publish but a later step stalls, the row is written from the confirmation rather than abandoned: the video is live, and the fields that come from those later steps can be null or false, with a warnings entry saying so.

Anything else gives you ok: false, a stable errorCode, a plain-English error telling you what to change, 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 run finishes as succeeded either way, because crashing would tell you less.

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

Because it drives the real Studio interface, a YouTube layout change can invalidate a selector. When that happens the run stops with LAYOUT_CHANGED naming the step and saves a screenshot plus the scrubbed HTML. It never reports a success it did not see. If it died mid-wizard it clicks Save and close first, so YouTube keeps one recoverable draft instead of an abandoned partial upload.

One more code worth knowing: DAILY_LIMIT is a per-channel 24-hour cap that YouTube has never published a number for, 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 videos need that disclosure, set it by hand in Studio afterwards.