Facebook Video Upload API: Page Videos and Reels
Facebook has two video upload flows for Pages. Reels use the video_reels edge in three phases: start a session, upload the file to rupload.facebook.com, then finish with video_state PUBLISHED and poll the video's status. Feed videos use the Resumable Upload API, then publish the file handle to the Page's videos edge. Both need pages_manage_posts. Adeli uploads both with one request and a status endpoint.
The Facebook video upload API is two separate flows, both part of Meta's Video API. Reels go through the Page's video_reels edge in three phases, with the file sent to rupload.facebook.com. Feed videos go through the Resumable Upload API, then get published to the Page's videos edge. Both need a Page access token with pages_manage_posts. The calls below are copied from Meta's docs, followed by the status fields to poll and the Reels specs.
This post covers video only. For text, link, and photo posts, see how to post to a Facebook Page with the API.
Which Facebook video upload flow do you need?
Pick by what the video should become on the Page. The two flows use different endpoints and hosts:
| Reel | Feed video | |
|---|---|---|
| Endpoint | POST /{page-id}/video_reels |
POST /{page-id}/videos |
| Upload host | rupload.facebook.com |
graph.facebook.com (Resumable Upload API) |
| Phases | start, upload, finish | start session, upload, publish |
| Length | 3 to 90 seconds | Not stated in Meta's Video API docs |
| Aspect ratio | 9:16 | Any |
| Daily limit | 30 API-published Reels per Page per 24 hours | Not stated in Meta's Video API docs |
| Scheduling window | 10 minutes to 29 days | 10 minutes to 6 months |
Sources: Meta's Reels Publishing guide and Page videos reference.
Reels can only be published to Facebook Pages. Personal profiles and Groups have no video publishing API.
What do you need before you start?
Both flows need the same Page setup:
- A Page access token from someone who can perform the
CREATE_CONTENTtask on the Page. Our Facebook Page access token guide shows how to get one that doesn't expire. - The permissions:
pages_show_list,pages_read_engagement, andpages_manage_posts, granted through Facebook Login. - Advanced Access for those permissions if the Page belongs to someone without a role on your app. That means App Review. The Facebook Graph API guide covers how access works.
- For feed videos only: your app ID and a user access token, because the Resumable Upload API opens its session on the app, not the Page.
The examples use API version v26.0, as Meta's docs do.
Step 1: Start a Reels upload session
POST to the Page's video_reels edge with upload_phase=start. Meta returns a video ID and the URL to send the file to:
curl -X POST "https://graph.facebook.com/v26.0/$PAGE_ID/video_reels" \
-H "Content-Type: application/json" \
-d '{"upload_phase":"start","access_token":"'"$PAGE_TOKEN"'"}'{
"video_id": "video-id",
"upload_url": "https://rupload.facebook.com/video-upload/video-id"
}Step 2: Upload the Reel to rupload.facebook.com
Send the file's bytes, or a public URL, to the upload host. The token goes in an Authorization: OAuth header, not the query string:
curl -X POST "https://rupload.facebook.com/video-upload/v26.0/$VIDEO_ID" \
-H "Authorization: OAuth $PAGE_TOKEN" \
-H "offset: 0" \
-H "file_size: $FILE_SIZE_BYTES" \
--data-binary "@reel.mp4"For a file you already host, replace the body with a file_url header:
curl -X POST "https://rupload.facebook.com/video-upload/v26.0/$VIDEO_ID" \
-H "Authorization: OAuth $PAGE_TOKEN" \
-H "file_url: https://cdn.example.com/reel.mp4"Meta's rules for hosted files:
- Public http or https, with no authentication on the URL.
- No Meta CDN URLs.
fbcdnlinks get rejected. - The host must allow the
facebookexternalhit/1.1user agent, and robots.txt can't block the file.
Both return {"success": true}.
Step 3: Finish and publish the Reel
POST to video_reels again with upload_phase=finish and a video_state. This is where the caption goes:
curl -X POST "https://graph.facebook.com/v26.0/$PAGE_ID/video_reels" \
-d "access_token=$PAGE_TOKEN" \
-d "video_id=$VIDEO_ID" \
-d "upload_phase=finish" \
-d "video_state=PUBLISHED" \
-d "description=Behind the scenes of launch day"| Parameter | What it does |
|---|---|
video_state |
PUBLISHED, DRAFT, or SCHEDULED |
description |
The Reel's caption |
title |
Optional title |
scheduled_publish_time |
Unix time, 10 minutes to 29 days out, with video_state=SCHEDULED |
place |
A Page ID to tag as the location |
is_ai_generated |
Labels the Reel as AI-generated. Defaults to false |
{"success": true} only confirms that Meta accepted the Reel. It goes live after processing, so poll the status.
Step 4: Poll the video status
GET the video with fields=status until processing and publishing both finish:
curl "https://graph.facebook.com/v26.0/$VIDEO_ID?fields=status&access_token=$PAGE_TOKEN"The status object has one overall value and three phases:
| Field | Values | What to watch for |
|---|---|---|
video_status |
uploading, upload_complete, processing, ready, error, upload_failed, expired |
ready means done; error or upload_failed means start over |
uploading_phase |
status, bytes_transfered, errors |
bytes_transfered is your resume offset |
processing_phase |
status, error |
Encoding problems surface here |
publishing_phase |
publish_status (published, scheduled, draft, error), publish_time |
published means it's on the Page |
Resuming an interrupted Reel upload: read uploading_phase.bytes_transfered and POST to the same rupload URL again with that number as the offset header.
What are the Facebook Reels video requirements?
A vertical 9:16 MP4 of 3 to 90 seconds. From Meta's Reels Publishing guide:
| Spec | Requirement |
|---|---|
| File type | MP4 recommended |
| Video codec | H.264 or H.265; VP9 and AV1 also supported |
| Aspect ratio | 9:16 |
| Resolution | 1080x1920 recommended, 540x960 minimum |
| Frame rate | 24 to 60 fps, fixed |
| Duration | 3 to 90 seconds (60 seconds for a Reel shared as a Story) |
| Encoding | 4:2:0 chroma subsampling, closed GOP of 2 to 5 seconds, progressive scan |
| Audio | AAC Low Complexity, stereo, 48 kHz, 128 kbps or higher |
Limits: 30 API-published Reels per Page in a rolling 24 hours, and 10 collaborator invitations per Page per day. Meta also runs a copyright check on uploaded Reels, which you can read with GET /{video-id}?fields=copyright_check_information. It can take a couple of minutes.
How do I upload a feed video to a Facebook Page?
Upload the file with the Resumable Upload API, then publish the file handle to /{page-id}/videos. Meta's video publishing guide uses three calls:
- Start a session on your app with the file's name, size in bytes, and type. Video must be
video/mp4:
curl -X POST "https://graph.facebook.com/v26.0/$APP_ID/uploads?file_name=launch.mp4&file_length=$FILE_SIZE_BYTES&file_type=video/mp4&access_token=$USER_TOKEN"The response is {"id": "upload:<UPLOAD_SESSION_ID>"}.
- Upload the bytes to the session. The token must be in the header, or Meta says the call fails:
curl -X POST "https://graph.facebook.com/v26.0/upload:$UPLOAD_SESSION_ID" \
-H "Authorization: OAuth $USER_TOKEN" \
-H "file_offset: 0" \
--data-binary "@launch.mp4"The response is a file handle: {"h": "2:c2FtcGxl..."}.
- Publish the handle to the Page with the Page token:
curl -X POST "https://graph-video.facebook.com/v26.0/$PAGE_ID/videos" \
-F "access_token=$PAGE_TOKEN" \
-F "title=Launch day" \
-F "description=What we shipped this week" \
-F "fbuploader_video_file_chunk=$FILE_HANDLE"The response is the video ID: {"id": "<VIDEO_ID>"}.
To resume an interrupted feed upload, GET /upload:<UPLOAD_SESSION_ID> with the same header. The response's file_offset tells you where to restart step 2.
Two things in Meta's docs to know before you build on this:
- The host is in flux. The publishing guide's step 3 uses
graph-video.facebook.com, while Meta's Video API overview says that host "has been deprecated" for video uploads. Test againstgraph.facebook.comtoo. - There's a shortcut for hosted files. The Page videos reference accepts a
file_urlparameter, so Meta fetches the video itself. It also takespublished,scheduled_publish_time, and athumbimage.
Feed videos are processed asynchronously too. Poll GET /{video-id}?fields=status as in Step 4.
Is there a faster way to upload video to Facebook?
Yes, if you'd rather not run two upload flows. Everything above sits on top of a Meta app, App Review for pages_manage_posts, a token for every Page, and a polling loop for every video. Review is the slow part. When we went through it for Pages publishing, reviewers cared most about the screencast, so record the finished Reel playing on the Facebook Page itself.
Adeli's Facebook API takes that work off your team. Adeli maintains the Facebook developer app, goes through Meta's review, and refreshes every connected Page's tokens. You send the video to POST /api/v1/posts and Adeli stages it on its own media host for Meta to fetch:
curl -X POST "https://app.tryadeli.com/api/v1/posts" \
-H "Authorization: Bearer $ADELI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"platform": "facebook",
"accountId": "00000000-0000-0000-0000-000000000000",
"message": "Behind the scenes of launch day",
"reel": { "contentType": "video/mp4", "base64": "<base64 MP4>" }
}'- Reel or feed video. Send
reelfor one vertical video published as a Reel, orvideofor a feed video, which also takes atitleof up to 255 characters. 202with apublishId. Video publishes asynchronously, so the response carries apublishIdand astatusUrlinstead of the finished post.- Size: up to 64 MiB of MP4 decoded from base64 in a JSON request.
Then poll Facebook video status with the publishId:
curl "https://app.tryadeli.com/api/v1/posts/facebook/status?accountId=$ACCOUNT_ID&publishId=$PUBLISH_ID" \
-H "Authorization: Bearer $ADELI_API_KEY"Adeli folds Meta's four status fields into one status: PROCESSING, READY, or ERROR. A READY response includes the permalink. An ERROR includes failureReason, which is Meta's own explanation of what was wrong with the video, usually its length or aspect ratio.
The same posting API also uploads video to YouTube, TikTok, Instagram, X, and Bluesky. On Adeli's pricing, your first 3 connected accounts are free, and one Facebook Page counts as one connected account.