Facebook Video Upload API: Page Videos and Reels

By Mika ReyesPublished 6 min read

The short answer

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_CONTENT task 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, and pages_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. fbcdn links get rejected.
  • The host must allow the facebookexternalhit/1.1 user 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:

  1. 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>"}.

  1. 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..."}.

  1. 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 against graph.facebook.com too.
  • There's a shortcut for hosted files. The Page videos reference accepts a file_url parameter, so Meta fetches the video itself. It also takes published, scheduled_publish_time, and a thumb image.

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 reel for one vertical video published as a Reel, or video for a feed video, which also takes a title of up to 255 characters.
  • 202 with a publishId. Video publishes asynchronously, so the response carries a publishId and a statusUrl instead 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.

faq

Frequently asked questions

How many Reels can a Facebook Page publish through the API?

30 per Page in a rolling 24-hour period. Meta enforces the limit on the video_reels endpoint, so the 31st Reel fails until the window moves forward. Feed videos posted to the videos edge aren't counted against it.

Can I upload a Facebook video from a URL instead of a file?

Yes. For Reels, send a file_url header to rupload.facebook.com instead of the file's bytes. The host must be public, allow the facebookexternalhit user agent, and not be a Meta CDN URL. For feed videos, the videos edge accepts a file_url parameter.

Can I post Reels to a personal Facebook profile with the API?

No. Meta's Reels Publishing API only publishes to Facebook Pages. There's no API for posting Reels or videos to a personal profile or a Group.

Can I schedule a Facebook Reel through the API?

Yes, on Meta's API. Finish the upload with video_state set to SCHEDULED and a scheduled_publish_time between 10 minutes and 29 days out. Feed videos accept scheduled_publish_time between 10 minutes and 6 months out.

Power your next project
with one social media API

Give your users publishing, scheduling, and analytics through one integration your team can actually maintain.

Try with agents

Start with free credits. No credit card required.