Social Media Uploader Pro - Post From Claude, ChatGPT or n8n avatar

Social Media Uploader Pro - Post From Claude, ChatGPT or n8n

Pricing

from $0.000013 / actor start

Go to Apify Store
Social Media Uploader Pro - Post From Claude, ChatGPT or n8n

Social Media Uploader Pro - Post From Claude, ChatGPT or n8n

An MCP server for posting video. Point Claude, ChatGPT or n8n at the Standby URL and nine tools show up. You pass your own logins per call; this server stores none. Each upload is a separate billable run on your own account, and a failed run still costs its start fee. On YouTube use the OAuth tool.

Pricing

from $0.000013 / actor start

Rating

0.0

(0)

Developer

Dami's Studio

Dami's Studio

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

3 days ago

Last modified

Share

Social Media Uploader Pro

Connect this to Claude, ChatGPT, Cursor, n8n or anything else that speaks MCP, then tell it to post the video.

"Put https://cdn.example.com/clip.mp4 on TikTok and Instagram, caption new drop today, make it public." The agent picks the right tool for each platform, starts the upload and hands you back a run id you can check on. No dashboard, no four tabs.

Nine tools show up in your client when you connect. Six of them post: TikTok, an Instagram Reel, a Facebook Reel, and three separate routes to YouTube. The other three read each uploader's live prices and tell you which ones your token can reach, say whether an upload actually published, and stop a run that is stuck.

This is a router, not an uploader. It does not open a browser and no megabyte of your video passes through it. It starts the right uploader Actor for the platform you asked for, hands it your input, and gives you back a run id you can poll.

Now the parts that decide whether this fits you.

The server holds no accounts. It has no logins of its own and it does not sign you in anywhere. You send the session cookies or the OAuth credentials for the platform you are posting to as arguments on the call, they go into the run that uses them, and nothing is kept here between calls. Read Your credentials below before your first real upload.

One call posts to one platform. Ask for four platforms and the agent makes four calls. Each one starts a separate, billable Apify Actor run on your own Apify account, at that uploader's own published prices. A run that fails to publish still costs that Actor's run-start fee, and where an Actor bills per megabyte, those megabytes cover the bytes it moved whether the video went up or not. Which events a given Actor bills differs per platform, and list_platforms reads the amounts off each Actor's live price list.

Make list_platforms your first call. Which of the six uploaders your token can reach is a per-token question, and that call answers it for your account. Some of them are still private to their publisher, and a tool being listed here does not mean your token can start it. list_platforms starts no upload and bills no upload event.

On YouTube, use the OAuth tool. upload_youtube_video_oauth goes through Google's official Data API, and the Actor behind it is public and carries no maintenance flag. The two browser-driven tools, upload_youtube_short and upload_youtube_video, are backed by Actors flagged under maintenance: no video has been published through either of them yet, because Google's "verify it's you" checkpoint trips on browser logins from cloud machines. Both are still listed and still callable, and neither is something to build on. Two routes to YouTube below has the detail, and the section after it explains why an unverified Google Cloud project stores every upload as private however you set the privacy.


How to connect

The server speaks the Model Context Protocol over Streamable HTTP. Give your MCP client the Standby URL with your Apify token in it, twice:

https://dami-studio--social-media-uploader-pro.apify.actor/mcp?token=<YOUR_APIFY_TOKEN>&apifyToken=<YOUR_APIFY_TOKEN>

Yes, twice, and here is why. Apify's Standby gateway sits in front of this container and needs a token of its own to let the request through. It takes the one you send, either as Authorization: Bearer or as ?token=, and then removes it before the request reaches this server — measured against the live endpoint on 2026-08-25, not read off a doc. So a token sent only the normal way authenticates you to Apify and arrives here as nothing, and every tool answers NO_APIFY_TOKEN. The second copy, under apifyToken, is the one this server actually reads. If your client can send custom headers you can use x-apify-caller-token: <YOUR_APIFY_TOKEN> instead of the second query parameter; both were verified working on the same day.

Claude Desktop, Claude Code or Cursor — add this to your MCP config:

{
"mcpServers": {
"uploader": {
"url": "https://dami-studio--social-media-uploader-pro.apify.actor/mcp?token=<YOUR_APIFY_TOKEN>&apifyToken=<YOUR_APIFY_TOKEN>"
}
}
}

n8n — use an MCP Client Tool node, transport Streamable HTTP, endpoint set to that same full URL. Leave authentication at none: the token is already in the URL twice and that is enough.

A quick way to check you got it right: ask for list_platforms. It starts no upload, bills no upload event, and comes back with the uploaders your token can reach. If it answers NO_APIFY_TOKEN, the apifyToken copy is missing.

Then ask the agent for what you want: "post cdn.example.com/clip.mp4 to TikTok with the caption new drop today, privacy public". It picks upload_tiktok, starts the run, and comes back with a run id.

The token decides everything. The child uploader runs on the account that token belongs to, bills that account, and reads that account's saved sessions. Every result carries billedToApifyUser so you can see which account that was before you are surprised by an invoice.

The first call after a quiet period is slow. The container idles out after fifteen seconds, so a poll a minute later starts a fresh one. Retry once and it is warm. Each of those starts bills this Actor's run-start fee of $0.0000125.


The tools

ToolWhat it does
list_platformsReports which of the six uploaders your token can reach, their live prices and their limits. Starts no upload run and bills no upload event.
upload_tiktokPosts a video to TikTok.
upload_instagram_reelPosts a Reel to Instagram.
upload_facebook_reelPosts a Reel to a Facebook Page or personal profile.
upload_youtube_shortUnder maintenance — no video has been published through it yet. Publishes a vertical clip as a YouTube Short, through a browser driven with your login cookies.
upload_youtube_videoUnder maintenance — no video has been published through it yet. Publishes a long-form video to YouTube, through a browser driven with your login cookies.
upload_youtube_video_oauthPublishes a video to YouTube through Google's official Data API, with your own OAuth credentials. No browser opens. Read Two routes to YouTube before you pick between this and the two above.
get_upload_statusReads a dispatched run: status, settled cost, and the uploader's own result row.
abort_uploadStops a dispatched run.

All nine are always listed. Whether a given upload tool can actually run is decided per call against your token, not at listing time.

Arguments every upload tool takes

ArgumentNotes
videoUrlPublic https link that serves the file itself, not a page with a player on it. You host it.
proxyConfigurationPassed to the uploader unchanged: useApifyProxy, apifyProxyGroups, apifyProxyCountry, proxyUrls. On the uploaders that bill per megabyte this is the lever that decides what that costs, so it is handed over verbatim rather than simplified.
demoModeStarts the uploader, validates the input and returns a labelled sample row. It does not verify your credentials and it does not upload anything. It still costs that Actor's run-start fee.
extraInputAn object merged into the child's input before your named arguments, so any field the uploader accepts is reachable even if there is no argument for it here. Non-secret fields only — Apify encrypts an input field at rest only if the target Actor's schema marks it secret, so a credential sent under a name the child does not mark secret is stored unencrypted in that run's INPUT record.
maxTotalChargeUsdThe spending cap for that child run. It belongs to the child run and is separate from any cap on this server.
runTimeoutSecsTime limit for the child run.
waitSeconds0 to 55. How long to hold the dispatch call open waiting for the run. Default 0, which returns as soon as the run exists.

Platform arguments

ToolIts own arguments
upload_tiktokcaption, privacy (public, friends, private), cookies, sessionid, sid_tt
upload_instagram_reelcaption, cookies, sessionid, ds_user_id
upload_facebook_reelcaption, reelTitle, target (profile or page), pageId, shareMode (now, schedule, draft), scheduledPublishTime, crosspostToInstagram, c_user, xs, datr, cookies, pageAccessToken, graphApiVersion, continueOnNonEnglishUi, proxyUrls
upload_youtube_shorttitle, description, tags, visibility, madeForKids, channelId, thumbnailUrl, allowSquare, allowAboveTargetResolution, containsThirdPartyAudio, verifyShortsClassification, cookies, resetProfile
upload_youtube_videotitle, description, tags, visibility, madeForKids, channelId, thumbnailUrl, channelIsPhoneVerified, publishAt, categoryId, playlistId, cookies, resetProfile
upload_youtube_video_oauthmethod (api or browser), title, description, tags, hashtags, privacyStatus, publishAt, categoryId, madeForKids, thumbnailUrl, channelId, clientId, clientSecret, refreshToken, cookies

Two Facebook facts are worth carrying over from that uploader, because they decide which lane your run takes. A personal profile is reachable only through the cookie lane; Meta publishes Reels to Pages only. And shareMode of schedule or draft needs pageAccessToken and pageId, because scheduling exists on the API lane and not in the composer.

The twelve individual YouTube cookie fields — SID, HSID, SSID, APISID, SAPISID, __Secure-1PSID, __Secure-3PSID, LOGIN_INFO, __Secure-1PSIDTS, __Secure-3PSIDTS, __Secure-1PAPISID, __Secure-3PAPISID — have no argument of their own here. Pass the whole Cookie-Editor export in cookies instead, which is what the browser uploaders' own instructions tell you to do. They are reachable through extraInput if you need them, subject to the encryption warning above.

What an upload tool gives you back

{
"ok": true,
"dispatched": true,
"published": null,
"platform": "tiktok",
"actorId": "i4vYTKFipy7khXR9M",
"runId": "aBcD1234efGh5678i",
"status": "RUNNING",
"datasetId": "9zYx...",
"consoleUrl": "https://console.apify.com/actors/runs/aBcD1234efGh5678i",
"billedToApifyUser": "your-apify-username",
"pricing": { "tiktok-upload": { "eventPriceUsd": 0.55 } }
}

published is null at dispatch and is never set to true there. A dispatch means the run started. It does not mean a video exists. Browser uploads take minutes, and the uploader only reports success after the platform's own confirmation, so the answer to did it post lives in get_upload_status.

pricing is read off the uploader's own live price list at dispatch time, not copied from this page.

get_upload_status

Takes runId, plus waitSeconds (0 to 55) and maxRows (1 to 20, default 5). It refuses any run that was not started by one of the six uploaders, so it cannot be used to read arbitrary runs on your account.

It returns the run's status, statusMessage, exitCode, startedAt and finishedAt; the platform's own chargedEventCounts and usageTotalUsd, which is the settled bill rather than an estimate; and the uploader's result row.

It also returns published, and that is derived from the row, never from the run status. Five of the six uploaders finish as SUCCEEDED even when they cannot post — a failed run costs the same start fee as a good one, so they spend it writing a diagnostic row instead of crashing. A SUCCEEDED run with ok: false published nothing. A demo row is reported as published: false with a line saying nothing was uploaded.

The sixth, behind upload_youtube_video_oauth, does the opposite. It writes a dataset row only for an upload YouTube confirmed, and puts a sample row, a notice or a diagnostic in the run's own key-value store instead. So a finished run of that one with no result row published nothing, and the reason is in that key-value record and the run log. get_upload_status says exactly that rather than telling you to poll again.

abort_upload

Takes runId and gracefully, which defaults to true. Same whitelist guard. A graceful abort gives the uploader its cleanup window, so it still writes the row that tells you where it stopped. On an uploader that bills by the megabyte a stuck upload keeps costing money, so this is worth reaching for rather than waiting out a timeout.

list_platforms

No arguments. It checks your token against all six uploaders in parallel and reads their live prices, memory tier, credential field names and publisher-documented size ceilings. No upload run starts and no upload event is billed. It is the first call to make when something is not behaving, because it answers which platforms this token can reach right now.


Two routes to YouTube

Three of the nine tools publish to YouTube. They do not all get there the same way.

upload_youtube_short and upload_youtube_video drive a real browser. You export your YouTube login cookies, the uploader opens YouTube Studio with them, and it fills in the upload form the way a person would. That route needs no Google Cloud project and no API credentials.

upload_youtube_video_oauth calls the YouTube Data API. You supply a clientId, a clientSecret and a refreshToken from your own Google Cloud project, and the upload goes through videos.insert. No browser opens. The same Actor also has a cookie route, reached by setting method to browser; its own README calls that route best-effort and points you at the API route for anything unattended.

The prices are far apart. On the two browser tools an upload costs $0.55 once YouTube confirms it, plus $0.02 for every megabyte the run transfers, plus a run-start fee of $0.015 per gigabyte of run memory — $0.03 on Shorts at 2 GB, $0.06 on long-form at 4 GB. Those megabytes are billed for bytes actually moved, on every outcome, published or not, so a failed upload still bills them. On upload_youtube_video_oauth an upload costs $0.05, the run start costs $0.01, and there is no per-megabyte event at all, so the size of your file does not change the bill. The OAuth prices here were read live off that Actor on 2026-08-24; the $0.55 and $0.02 come from the browser uploaders' own source, read the same day. list_platforms reads all of them live for your token, which is the current answer rather than this page's.

Which route can publish today is the part that should decide it for you. As a publisher statement dated 2026-08-24, and not something this server measured: both browser tools are flagged under maintenance, no video has been published through either of them successfully yet, and the reason given is that Google's "verify it's you" checkpoint trips on browser logins from cloud machines. There is no date for that changing and this page will not invent one. The Actor behind upload_youtube_video_oauth carries no maintenance flag, and observed on the Apify API on 2026-08-24 it is public, is not deprecated, and has 111 runs from 3 accounts, 2 of them active in the last 30 days. So the OAuth route is the YouTube route to reach for. It is also the cheaper one.

It has one condition attached, on Google's side rather than here, and the next section is that condition. Read it before you send anything public through it.

An unverified Google project makes every video private

This one catches people after they have paid, so it belongs here rather than in a footnote.

Every Google Cloud project starts out unverified. While it is, YouTube stores every video uploaded through the Data API as private, whatever privacy you asked for. The upload succeeds. The $0.05 upload fee is charged. The video exists, and nobody but you can watch it. There is no appeal and no setting on your side that undoes it. The one way out is Google's YouTube API compliance audit, which you request and Google decides.

The uploader does the one honest thing open to it: it reads back the privacy YouTube actually stored and reports it. Three fields on the result row from get_upload_status carry the answer — requestedPrivacy is what you asked for, privacyStatus is what YouTube kept, and privacyOverridden is true when those differ. Check privacyOverridden before you tell anyone the video is live. Without it a forced-private upload and a private upload you asked for look identical.

A second Google setting bites a week later. An OAuth consent screen left in Testing issues refresh tokens that expire after 7 days. Move the consent screen to In production and the token keeps working.

Both of these are documented by that uploader's own README and source, read on 2026-08-24. This server did not measure either of them.

upload_youtube_video_oauth in detail

You give it a public https URL to the video file and your own Google OAuth credentials. It starts the uploader on your account, which downloads the file, authenticates with your refresh token, and calls videos.insert. It sets the title, description, tags, hashtags, category, privacy, the COPPA audience declaration, a scheduled publish time and a custom thumbnail in the one run. Hashtags are appended to the end of the description. Privacy defaults to private, which is the uploader failing closed rather than publishing something you did not intend.

publishAt schedules the video. YouTube only schedules private videos, so a schedule set alongside public or unlisted has the privacy coerced to private for you, which keeps the schedule working.

method picks the route. Leave it unset and it is worked out from the credentials you sent: the OAuth trio means api, cookies alone mean browser. That inference matters, because the uploader's own default is api — a cookies-only call that left method unset would run as an OAuth call with no OAuth credentials, and that uploader answers missing credentials with a labelled sample row and a run that SUCCEEDS. You would pay a start fee for a run that uploaded nothing.

Two shapes are refused before anything starts, so they cost nothing at all. Sending both kinds of credentials without setting method returns UPLOAD_METHOD_AMBIGUOUS — picking one would throw the other away in silence, so the choice is handed back to you. Setting method to one route while sending only the other route's credentials returns UPLOAD_METHOD_CREDENTIAL_MISMATCH. Both start no run and bill nothing.

The dispatch reply is the same shape as every other upload tool. The result row you read back later carries ok, method, videoId, watchUrl, studioUrl, title, privacyStatus, requestedPrivacy, privacyOverridden, scheduledFor, thumbnailSet, uploadSeconds and proxyNotice, plus errorCode, error and hint when something went wrong.

Two more things that uploader documents about itself, read on 2026-08-24 and not measured here. Its download guard refuses a source file over 10240 MB on either route, and on the API route the upload request carries a 4096 MB body limit, which is the binding ceiling there. And its price list currently holds a record scheduled to start on 2026-09-15 that renames the upload event; the amounts are identical either side of that date, but the event name in a chargedEventCounts from a run made before it will not match the name list_platforms reports. list_platforms flags that on this platform, and the authoritative answer for any run you make is chargedEventCounts and usageTotalUsd on the finished run.


Your credentials

Session cookies and OAuth credentials arrive as tool arguments, go straight into the request that starts the child run over HTTPS, and land in that run's INPUT record. Every credential field on all six uploaders is marked secret in its schema, and Apify encrypts secret input fields before saving them to a run's key-value store.

A Google refresh token deserves a sentence of its own. How long it lasts depends on that consent screen setting above: once the screen is In production the token does not expire on a timer, and keeps granting upload access to your channel until you revoke it in your Google account — while the screen is still in Testing it dies after 7 days. Either way, treat it as more sensitive than a password.

This server writes nothing of its own. It does not push a dataset row, it does not write to its key-value store, and it keeps no cache of your arguments. Its log records the tool name, the platform, the resulting run id and the names of the arguments you supplied — never a value, never a truncated value, never a length. Before any upstream error text is returned to you or written to the log, every exact occurrence of a secret you supplied on that same call is replaced with [redacted].

Each uploader has its own instructions for exporting cookies or creating OAuth credentials, and they differ. Read the one for the platform you are posting to before your first real run.


What it costs

This Actor takes no fee of its own. Its one priced event is the platform's run-start fee, $0.0000125. Apify bills one start event per gigabyte of run memory, minimum one, and this Actor is pinned to 256 MB, so a start bills exactly one event. Standby keeps the container warm between calls, so a busy connection starts it rarely. There is no per-dispatch fee, no per-upload fee and no markup.

The real bill is the uploader you dispatched. It runs on your own Apify account at the prices on that Actor's own store page, exactly as if you had started it yourself.

Take one 30 MiB TikTok post, published, on that uploader's default residential exit. Prices read live on 2026-08-24:

LineCost
This server's run start, if the call had to boot it$0.0000125
tiktok-video-uploader-pro run start — 2 events at $0.015, because that Actor is fixed at 2 GB$0.03
tiktok-upload, once TikTok confirms the post$0.55
30 video-megabyte events at $0.02$0.60
Total$1.18

This server is $0.0000125 of that. That is one eight-hundredth of a cent.

Put your own servers in proxyConfiguration.proxyUrls and the per-megabyte line does not apply, so the same post is $0.58. If TikTok never confirms it, the upload fee and the megabytes are not charged and the child run costs $0.03, the start fee it bills whatever happens.

Instagram's flat event is $0.40, on the same $0.03 start and the same $0.02 per megabyte, also read live on 2026-08-24. Megabytes are counted in whole MiB, rounded up.

The two YouTube routes are further apart than any other pair here. One published 200 MiB long-form video:

Lineupload_youtube_video (browser)upload_youtube_video_oauth (OAuth)
Child run start$0.06 — 4 events at $0.015, that Actor runs at 4 GB$0.01 — 2 events at $0.005, that Actor runs at 2 GB
Upload fee, once YouTube confirms$0.55$0.05
200 MiB transferred$4.00 — 200 events at $0.02none, that Actor has no per-megabyte event
Total$4.61$0.06

On TikTok, Instagram and Facebook the flat fee and the megabytes fire only after the platform confirms the post. On YouTube Shorts and the browser YouTube tool the per-megabyte charge is billed for bytes actually transferred on every outcome, including a failed upload and a transfer that had to restart, so those runs can bill more megabytes than the file holds. upload_youtube_video_oauth has no per-megabyte event to bill. Its upload fee is charged per row that uploader writes to its dataset, and its source writes a row there only once YouTube has returned a video id. Its published README says something different about one case — that the credential-free sample row is billed at the upload price — and this page will not call that free while the two disagree, so budget for a demoMode or credential-free call on it costing the upload fee as well as the start fee.

Those child-billing rules are read from each uploader's own source and store page. This server does not measure them.

Call list_platforms before you commit to anything: it reads each uploader's live prices off the platform for the token you are using, starts no upload run and bills no upload event.


Reachability

Before starting anything, every upload tool asks the Apify API whether your token can see that uploader. If it cannot, the request answers HTTP 404 record-or-token-not-found, "Actor was not found or access denied", and the tool returns an error, starts no upload run and bills no upload event. Apify gives that same answer whether an Actor does not exist or exists and is not shared with your account, and it does not distinguish the two — so the error says what was observed and stops there.

list_platforms will show you exactly which of the six your token reaches on any given day, which is always current in a way this page cannot be.


When it breaks

Start with list_platforms. It starts no upload run and bills no upload event. It names the account you are authenticated as, and tells you which uploaders that account can reach.

What you seeWhat it means
HTTP 400 actor-standby-mode-not-enabledStandby is off on this Actor. Turn it on in the Actor's Standby tab. A rebuild can switch it off.
NO_APIFY_TOKENNo token reached the server. The Apify gateway strips the token you authenticate with, so the URL needs a second copy: ?token=<TOKEN>&apifyToken=<TOKEN>. See How to connect.
ACTOR_NOT_ACCESSIBLEThe accessibility check answered 404 for your account. No upload run was started and no upload event was billed. See Reachability above.
APIFY_TOKEN_INVALIDThe Apify API did not recognise the token. Check for a truncated paste.
APIFY_TOKEN_FORBIDDENThe API refused the token for this action. The message carries the API's own words for it.
APIFY_PAYMENT_REQUIREDThe API answered with a payment gate.
UPLOAD_METHOD_AMBIGUOUSupload_youtube_video_oauth was sent both OAuth credentials and cookies with no method. Set method and send only that route's credentials. Nothing was started and nothing was charged.
UPLOAD_METHOD_CREDENTIAL_MISMATCHupload_youtube_video_oauth was sent a method and the other route's credentials. Match the two. Nothing was started and nothing was charged.
NOT_AN_UPLOAD_RUNThe run id you passed to get_upload_status or abort_upload was not started by one of the six uploaders.
ACTOR_PREFLIGHT_UNEXPECTEDThe check came back with a status this server does not have a rule for. It hands you the status and the start of the body rather than guessing.
INTERNALSomething failed inside this server. Send the run id.
First call hangs or times outCold start. The container idles out after fifteen seconds, so an idle server has to boot. Retry once.

If the dispatch succeeded and the upload did not, the answer is in the child run, not here. Call get_upload_status and read errorCode and error on the row — every uploader documents its own codes on its own page, and they name the thing to change. Before you retry a run that ended in UPLOAD_UNCONFIRMED, open the account and look: a blind retry can double-post.


What this does not do

It does not create, cut or host your video. You host the file and pass a public https link.

It does not schedule the call. One call starts one upload, now. Facebook's schedule and draft modes and YouTube's publishAt are passed through to those uploaders, and are the exception.

It does not check whether your credentials are alive. Nothing here can, without spending a run. demoMode validates input and returns a sample row; it does not test a session or a refresh token, and reporting otherwise would be a fake success.

It does not post to more than one platform per call. Ask the agent for six posts and it makes six calls, each its own run, each billed on its own.

It does not hold your credentials between calls. What persists is whatever the uploader itself saves, in your own Apify account, under the rules on that uploader's page.