Facebook Reels Uploader avatar

Facebook Reels Uploader

Pricing

from $350.00 / 1,000 reel published (browser)s

Go to Apify Store
Facebook Reels Uploader

Facebook Reels Uploader

Publish a Reel to your Facebook Page or profile from a URL. A Page token uses Meta's official Graph API (cheaper, no browser); cookies drive the composer, the only route to a profile. The publish fee needs a confirmed publish; metered bandwidth is billed per megabyte on every outcome.

Pricing

from $350.00 / 1,000 reel published (browser)s

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

Publish a Reel to a Facebook Page or to your personal profile, from a public video URL.

The Actor ships two completely different routes to Facebook and picks one from your input:

  • The Graph API lane. You supply a Page access token. The Actor calls Meta's official Reels Publishing API: no browser, no proxy, real Meta error codes, and support for scheduling and drafts. This lane is faster, far more reliable and much cheaper. Use it whenever you can.
  • The browser lane. You supply your own exported Facebook session cookies and the Actor drives the web composer. This is the only route that can reach a personal profile — and it carries a real account risk, described below in Read this before you use cookies.

Every run writes exactly one dataset row and finishes as SUCCEEDED, even when it fails: a failed run costs you the same platform start fee as a successful one, so this Actor spends that fee telling you precisely what went wrong instead of crashing.


Read this before you use cookies

Driving facebook.com with injected cookies is against Meta's Terms of Service and its Automated-Activity policy. The realistic failure mode is not a broken button. It is a checkpointed or disabled account — and if you were publishing to a Page, the Page goes with it. Meta's error 368 ("action deemed abusive or otherwise disallowed") is an integrity block, not a rate limit: retrying makes it worse and lengthens it.

Three things follow from that, and they are recommendations, not decoration:

  1. Use the API lane for Pages. It is the supported route, it needs no App Review (see below), and it puts nothing at risk.
  2. If you must use cookies, use a segregated account — one you can afford to lose, not the account your business depends on.
  3. Space uploads out. No more than roughly ten a day per account through the UI.

The browser lane exists because Meta offers no alternative for personal profiles. It does not exist because it is a good idea.


How the lane is chosen

What you supplyLane usedWhy
pageAccessToken + pageIdGraph APISupported, no browser, no proxy, real error codes, drafts and scheduling, and Meta fetches the video itself
Cookies + target = profileBrowserThe only route that exists for a personal profile
Cookies + target = page, no tokenBrowserYour choice — the run logs a loud pointer at the API lane
BothGraph APIBetter on every axis
NeitherDemoA labelled sample row. Nothing is downloaded, nothing is uploaded and nothing is charged

Reels can only be API-published to Pages — never to a personal profile. Meta states it flatly: "You can only publish Reels to Facebook Pages." The publish_actions permission that once allowed profile posting was removed in Graph API v3.0 in 2018 and has not returned. There is no permission, no review tier and no token that unlocks it. If you pass a token with target = profile, the Actor tells you so up front rather than failing later for a reason that sounds like a bug.


You do not need App Review. Meta's own documentation says: "If your app will only be used by app users who have a role on the app itself, App Review is not required." You create the app, you are its admin, and you admin the Page — so Standard Access is enough, Business Verification does not apply, and no screencast has to be submitted to anyone. The setup is one-time and takes about ten minutes.

  1. Go to developers.facebook.com and log in with the account that admins the Page.
  2. My Apps → Create App. Pick the Business app type. Name it anything.
  3. In the app dashboard, add the Facebook Login product (you only need it to mint a token).
  4. Open the Graph API Explorer (Tools → Graph API Explorer) and select your new app.
  5. In Permissions, add: pages_show_list, pages_read_engagement, pages_manage_posts.
  6. Click Generate Access Token and complete the dialog, choosing the Page you want to publish to. This gives you a short-lived user token.
  7. Switch the token dropdown from User Token to Page Token and pick your Page. The token in the box is now a Page token — this is the one that matters. A user token with the same permissions is not enough.
  8. Open the Access Token Debugger (Tools → Access Token Debugger), paste the token and click Extend Access Token. You now hold a long-lived Page token, which does not expire on a clock.
  9. Confirm your role: GET /me/accounts?fields=id,name,tasks must list your Page with CREATE_CONTENT in its tasks array. A token from an ANALYZE- or ADVERTISE-only role authenticates perfectly and then fails at publish time with error code 200.
  10. Paste the token into Page access token and the Page's numeric id into Page ID.

Long-lived Page tokens do not expire on a schedule, but they are killed by a password change, by revoking the app's permissions, or by a Meta security invalidation. When that happens the run reports API_TOKEN_INVALID and you regenerate it with the same steps.

On this lane the Actor hands Meta your videoUrl and Meta downloads the file itself, so nothing large moves through the run — which is why this lane is priced so much lower.

If Meta cannot fetch your URL, the run does not fail. Meta fetches anonymously, so a signed link that has expired, a host that blocks Meta's fetcher, or a URL with a username and password embedded in it will all refuse that fetch. When that happens the Actor downloads the video itself and uploads the bytes to Meta directly, resuming from Meta's own byte offset if the transfer is interrupted (up to three attempts). It is slower and it is the only case where a file moves through this lane, so a publicly fetchable https URL is still the path worth aiming for — but a URL Meta cannot reach is a detour, not a dead end.


The browser lane: exporting your cookies

Only needed if you are publishing to a personal profile, or if you would rather not create an app. Read Read this before you use cookies first.

  1. Open facebook.com in Chrome or Edge, signed in as the account that will publish.
  2. Press F12 to open DevTools, then go to Application → Storage → Cookies → https://www.facebook.com.
  3. Find the row named c_user and copy its Value column. That is your numeric account id. Paste it into the Facebook "c_user" cookie field.
  4. Find the row named xs and copy its Value. It looks like 12%3Aabc...%3A2%3A1690.... Paste it exactly, including every %3A, into the Facebook "xs" cookie field.
  5. Find the row named datr and copy its Value into the Facebook "datr" cookie field. This one is optional but strongly recommended: datr is how Facebook recognises the browser that normally uses this account. A session arriving without it looks like a brand-new device and is much more likely to be met with a security checkpoint. Never invent a value — a datr Facebook has never seen is worse than none at all.

Prefer to hand over the whole jar? Export the entire facebook.com cookie list with a cookie manager extension and paste the JSON into Whole Facebook cookie jar. Export it from a facebook.com tab where you are signed in: messenger.com and instagram.com are separate cookie jars and will not work.

Filling both is fine and nothing gets silently dropped. If you paste a raw Cookie: header, the two are merged and the named fields win where they overlap. If you paste a JSON export that already contains c_user and xs, the export is used whole — it is the richer jar — and the run says so in the log rather than quietly ignoring what you typed.

Three more things worth knowing:

  • Set the proxy country to the country where the cookies were created. A country jump is the single biggest checkpoint trigger this Actor sees. The browser's timezone and locale are set from that same country, so the environment and the exit tell Facebook one consistent story rather than two contradictory ones.
  • Your cookies are secret input fields. They are never written to the dataset, never written to the key-value store, and never printed in the log — the log reports only a length, like <42ch>.
  • Cookies are revoked by logging out, by "Log out of all sessions", and usually by a password change. When that happens the run reports SESSION_INVALID and you re-export.

What gets checked before anything is transferred

Meta rejects a surprising number of technically-fine videos, and it does it after the upload. This Actor checks the file first, so a doomed clip costs you the start fee instead of a full run:

CheckRule
Duration3 seconds minimum. On the API lane, 90 seconds maximum — Meta's 2025 "no length limit" announcement applies to the Facebook app, not to the API. On the browser lane, over 90 s is a warning and 40 minutes is the hard ceiling
Aspect ratio16:9 through 9:16 inclusive. Anything outside is refused; anything that is not vertical 9:16 gets a warning, because it will be pillar- or letterboxed in the Reels player
Resolution540x960 or larger
Frame rate24-60 fps, and constant. A variable frame rate is the single most common silent Reels rejection — screen recorders, phone slow-motion and AI generators all emit it. Fix: ffmpeg -i in.mp4 -r 30 -vsync cfr -c:a aac -b:a 128k out.mp4
AudioAAC-LC, 128 kbps+, 48 kHz stereo is what Meta specifies. Anything else is a warning, not a refusal — Facebook will transcode it
File1 KB minimum and 2 GB maximum are Meta's own bounds, and it has to actually be a video. This Actor also applies its own ceiling to any file it has to move itself — see "Notes and limits"
Caption2,200 characters. Meta publishes no documented limit for Reels descriptions, so this is a deliberately conservative ceiling

What "confirmed" means here

A closed composer is not a published reel, and a 200 from Meta's finish call is not one either. Transcoding and integrity review both happen asynchronously, and both can reject a video minutes after the upload appears to have worked.

So the Actor confirms the publish before it reports success:

  • Browser lane — it lists the reels on the account before publishing, then polls for up to three minutes afterwards for a /reel/<id> that was not there before. If none appears, the run reports UPLOAD_UNCONFIRMED, tells you to check the account before retrying so you do not double-post, and charges no publish fee. The video had already been pushed to Facebook by that point, so the bandwidth it used is billed by the megabyte; the row's chargeNote gives the figure.
  • API lane — it polls Meta's own status endpoint until publishing_phase.status is complete. If Meta reports a processing error instead, or never reports the reel as published, the run reports API_PROCESSING_FAILED and charges nothing at all — the API lane has no metered connection and never bills per megabyte. Where Meta issued a video id for the upload, that id is in the message and in the row's reelId, so you can check the Page for it instead of retrying blind and posting the reel twice.

The caption is read back off the composer before publishing, and the "also share to Instagram" toggle is read back too. If the caption cannot be verified, or the Instagram toggle's state cannot be read, the Actor refuses to publish: a reel with the wrong caption, or a post to a platform you did not ask for, is worse than no reel.


What we cannot detect

Music and rights takedowns produce no signal at all. A reel can publish successfully, and then be muted, geo-blocked or suppressed minutes later because its audio matched a rights holder's catalogue. Meta emits no error, no webhook and no status change for this. The Actor reports the publish it confirmed — it cannot promise the reel is still playable an hour later, and it does not pretend to.

Reach, distribution and whether Facebook shows your reel to anyone are likewise outside anything an uploader can see.


Pricing, and why there is a per-megabyte event

Three events. The publish fee is charged only for a publish that really happened; the bandwidth event is charged for bandwidth that really moved, which is not the same thing:

EventFires
facebook-reel-uploadBrowser lane, once, only after a new reel id is confirmed on the account
facebook-reel-upload-apiAPI lane, once, only after Meta reports the reel as published
video-megabyteBrowser lane, only when the run used this Actor's metered residential exit — for the megabytes that run actually moved, on every outcome, including a run that published nothing

A run that publishes nothing is never charged a publish fee. That has not changed and will not.

A run that publishes nothing is still charged for the bandwidth it had already spent, when that bandwidth crossed this Actor's own metered residential connection. This is a change from how the Actor behaved before 2026-08-25, and it is worth being plain about why. Reaching Facebook's composer downloads your video and pulls tens of megabytes of Facebook's own interface across a residential connection that is billed to this Actor by the gigabyte. Those bytes are bought the moment they move. If your cookies turn out to be dead at the composer, or Facebook refuses the clip, or the upload is never confirmed, the bytes are gone either way — and previously the run absorbed all of them and told you it had charged you nothing. It now bills for what it moved, and says so on the row.

Four things bound that charge, and each of them is enforced in code, not promised in prose:

  1. Measured bytes only. The count comes from bytes this run actually moved, counted as they moved. Not the file size you declared, not the size the host advertised, and never an estimate. A leg that could not be measured is billed as zero, not guessed at.
  2. Only bytes this Actor paid for. Bytes that crossed a connection you supplied are never billed — see "When there is no metered connection" below. And the download from your own video host, which is ordinary transfer rather than residential bandwidth, is billed at what it cost, not at the per-megabyte rate: a 500 MB download that died mid-transfer recovers about $0.10, not $10. That download is requested uncompressed (accept-encoding: identity) precisely so the byte count is a wire count rather than a decompressed one; if your host sends a compressed body anyway, the leg is marked unmeasured and billed as zero. And a download too small to be worth a whole event rounds to nothing rather than up to one.
  3. Never more than success would have cost. A failed run is never billed more than a confirmed publish of the same file would have been billed.
  4. Never past your own limit. If you set maxTotalChargeUsd, the charge is clamped to what is left inside it, and the row and CHARGE_SUMMARY.json both say how many megabytes were dropped and absorbed by this Actor.

And the run still refuses before spending, not after. If your maxTotalChargeUsd cannot cover the publish fee, the run stops before it contacts Meta, opens a proxy or downloads anything, and charges nothing at all — charging on failure is about bytes that already moved, never a licence to spend first and bill afterwards. The same check turns your limit into a file-size ceiling, so a file too expensive for your budget is refused before a byte of it is pulled.

The Pricing tab carries the amounts that are actually billed; the current design prices are $0.35 for facebook-reel-upload, $0.15 for facebook-reel-upload-api and $0.02 for video-megabyte.

Why the API lane is priced far below the browser lane. It genuinely costs a fraction as much to run: no browser, no proxy bandwidth, and with the API's file_url upload Meta fetches your video itself, so not one megabyte of it moves through this Actor.

Why the browser lane charges per megabyte, honestly. On that lane the composer session normally runs through a residential connection — a datacenter address on a write action is what gets accounts checkpointed — and your video crosses that same metered connection on its way to Facebook. That bandwidth is billed to this Actor by the gigabyte and it scales directly with your file. At the design prices, the flat fee alone stops covering the cost of a run at roughly a 20 MB video, and a 90-second 1080x1920 H.264 reel is normally 20-100 MB. In other words the median customer file sits exactly at that break-even point. The per-megabyte event is what keeps the flat fee from having to be much higher for everybody, and it is why a large file is expensive here rather than refused.

When there is no metered connection, there is no per-megabyte charge — on success or on failure. The event exists to recover one specific cost, so it is charged only when that cost is really incurred. You pay the publish fee and nothing per megabyte whenever the run did not use this Actor's residential exit — that is, when you supplied your own servers in proxyUrls, when you asked for a datacenter-only proxy group, or when you switched the Apify proxy off. A failed run on any of those exits is charged nothing at all. The run says so in the log, and the result row reports videoMegabytes: 0 with bandwidth.metered: false. The API lane never charges it either, for the same reason: the bytes do not cross a metered connection there, so billing you for them would be billing you for a cost that was never incurred.

What is not covered, stated rather than glossed over. If you abort a run, this Actor tries to write its row and settle the bandwidth inside the few seconds the platform allows before it kills the container — that is best effort and it can be cut short. A platform migration and the platform's own hard run timeout get no such attempt at all: in those cases the bandwidth is absorbed by this Actor, and no charge and no row are made.


Output

One row per run. On success:

{
"ok": true,
"lane": "api",
"target": "page",
"reelId": "1234567890123456",
"reelUrl": "https://www.facebook.com/reel/1234567890123456",
"caption": "Behind the scenes #reels",
"videoMegabytes": 0,
"confirmed": true,
"chargeStatus": "pending",
"chargeSummaryKey": "CHARGE_SUMMARY.json",
"publishedAt": "2026-08-23T10:12:44.109Z",
"warnings": []
}

Two fields are worth reading carefully:

  • videoMegabytes is the megabyte count this Actor asked to bill, not the size of your file — and not, on its own, proof of what you were billed. On a successful run it is the file's megabytes; on a failed one it is the megabytes of bandwidth that had already moved. It is 0 whenever no per-megabyte event applied. The file's own size is in videoSizeMegabytes; the count the platform actually charged is megabytesCharged in the settled record described next.
  • chargeStatus is pending in the row itself, and stays that way. The row is written before the charge is made, because this Actor charges only after a result has actually been delivered to you — so the row cannot know what the platform answered, and an Apify dataset row cannot be edited afterwards. Rather than leave you guessing, the row names where the answer landed: CHARGE_SUMMARY.json in the run's key-value store, which carries chargeStatus: "settled", the reel's id and URL, and the counts the platform actually charged. The same figures are printed in the log as a Billed: line and set as the run's status message. If an event has no price, or your own maximum-charge limit was reached, that is where you will see it — the run says "you were NOT billed" rather than quietly claiming otherwise.

On failure, one diagnostic row with ok: false, an errorCode and a sentence you can act on. It carries no publish fee, and it carries a bandwidth block plus a chargeNote stating in words what is being billed for the megabytes the run had already moved and why — with the settled figure, as always, in CHARGE_SUMMARY.json:

{
"ok": false,
"errorCode": "UPLOAD_REJECTED",
"charged": null,
"chargeStatus": "pending",
"videoMegabytes": 32,
"bandwidth": {
"exit": "residential",
"metered": true,
"inboundBytes": 20971520,
"inboundMeasured": true,
"inboundEncoding": null,
"meteredWireBytes": 12582912,
"meteredWireMeasured": true,
"meteredOutBytes": 20971520,
"meteredOutMeasured": true,
"megabytesBillable": 32,
"megabytesRequested": 32,
"capTruncated": false,
"cappedAtSuccessCost": false
}
}

Any ...Measured: false in that block means that leg could not be measured on this run, and an unmeasured leg is billed as zero — never estimated from the file size or from a declared length. inboundMeasured: false has one specific cause: your video host answered with a compressed content-encoding even though this run asked it for the file uncompressed, which means the only byte count available here is the decompressed one and the number of bytes that really crossed the wire is not knowable. inboundEncoding names the coding the host used. The browser lane also saves a screenshot and the page HTML to this run's key-value store, named after the error code, so a Facebook layout change can be diagnosed without repeating the run.

Error codeWhat it means
BAD_INPUTSomething in the input is wrong; the message names the field and the fix
OVERSIZED_VIDEOAbove this Actor's file ceiling. The ceiling is set by the Actor's owner and no input raises it — the video is downloaded straight from its host into this Actor before any proxy is involved, so the cost it caps is the same whichever exit the run uses. On the API lane, hosting the file where Meta can fetch it anonymously removes the limit entirely, because then none of it moves through this Actor
VIDEO_UNAVAILABLEThe videoUrl did not deliver a file (HTTP error, timeout, expired signed link)
SESSION_INVALIDThe cookies are not a logged-in session. Re-export them
SESSION_CHECKPOINTFacebook is showing a security checkpoint. Clear it by hand, then re-export
SESSION_2FA_REQUIREDThe account wants a 2-factor code, which no cookie can supply. Supply the real datr
ACTION_BLOCKEDAn integrity/abuse block, not a rate limit. Do not retry — wait at least 24h
COMPOSER_UNAVAILABLEDesktop reel creation is not available on this personal profile; Facebook gates it per account. Use a Page, or post from the mobile app
LOCALE_UNSUPPORTEDThe Facebook interface is not in English and a step needed a text label
LAYOUT_CHANGEDA read-back failed — Facebook changed the page. The failing locator is named in the message
CROSSPOST_UNCONFIRMEDThe Instagram toggle could not be verified, so nothing was published
UPLOAD_STALLEDFacebook never finished processing the video
UPLOAD_REJECTEDFacebook refused the clip, usually a duration or format problem
UPLOAD_UNCONFIRMEDThe composer closed but no new reel appeared. Check the account before retrying
TIMED_OUTThe run hit its time budget before Facebook confirmed anything
ABORTEDYou aborted the run. Written on a best-effort basis inside the seconds the platform allows before it kills the container, so it may not appear. Check the account before re-running: an abort can land after the composer has already posted
NETWORKThe proxy or DNS failed
API_TOKEN_INVALIDGraph error 190 — regenerate the Page token
API_PERMISSIONThe token lacks CREATE_CONTENT on that Page, or is a user token rather than a Page token
API_RATE_LIMITMeta throttled the Page. Note that Meta allows 30 API-published reels per Page per rolling 24 hours, and scheduled reels count against the same budget
API_ABUSE_BLOCKGraph error 368. Hard stop, and retrying looks more abusive
API_SPEC_REJECTMeta rejected the video's format. Permanent — re-encode, do not retry
API_UPLOAD_FAILEDThe transfer to Meta failed after three resume attempts
API_PROCESSING_FAILEDMeta accepted the upload and then failed to process it, or never reported it as published. The reel is NOT live, and nothing is charged — the API lane bills no publish fee here and never bills per megabyte. When Meta gave the upload a video id, the message and the reelId field carry it — check the Page for that reel before you retry, because a second run would upload it again

Notes and limits

  • Memory is fixed at 2048 MB so the platform's per-gigabyte start fee cannot change under you.
  • The video is downloaded straight from its origin, never through the proxy — proxying a file download costs many times more and buys nothing.
  • One sticky proxy exit is used for the whole run. Rotating mid-session is itself a checkpoint trigger.
  • Bring your own proxy servers if you prefer. Put them in the proxy setting's proxyUrls, or set the top-level proxyUrls field — both are honoured, and your servers are used verbatim, in the order you give them. What that saves you is precise: the outbound leg no longer crosses this Actor's metered residential connection, so the per-megabyte charge does not apply to that run — on a confirmed publish or on a failure. It does not raise the file size limit — it lowers it, from 500 MB to 304 MB. The inbound leg is unchanged either way (the video is pulled straight from its host into this Actor before any proxy is involved), but an unmetered run earns only the flat fee, with no per-megabyte line to help carry a large file, so a smaller file is all that fee can cover. Selecting a non-residential proxy group, or switching the proxy off entirely, has the same effect for the same reason.
  • The file ceiling is set by this Actor's owner, not by an input field — an input that could raise it would let anyone raise the owner's bandwidth bill without limit. On the browser lane it is 500 MB on the default metered residential exit and 304 MB on any unmetered one (your own proxyUrls, a non-residential group, or the proxy switched off). On the API lane, file_url uploads are bounded only by Meta's own limits (1 KB to 2 GB), because Meta fetches the file and none of it passes through this Actor; if Meta cannot fetch your URL and the bytes have to be pushed from this run instead, a smaller ceiling applies to that fallback — currently 107 MB. None of these numbers is typed in by hand: each is derived from what that lane's fee can actually pay for, and the OVERSIZED_VIDEO message always names the exact figure that applied to your run. Hosting the video where Meta can download it anonymously removes the limit entirely.
  • Desktop reel creation on personal profiles is gated by Facebook per account. If the composer does not render for yours, the Actor reports COMPOSER_UNAVAILABLE rather than clicking blindly. Publish to a Page, or post that clip from the mobile app.
  • This Actor publishes; it does not download, scrape or repost anyone else's content. Publish only video you have the rights to.