Facebook Reels API: start, upload, finish and the video_state values
Publishing a Facebook Reel takes three calls: upload_phase start, a file upload to rupload, then finish with video_state PUBLISHED, SCHEDULED or DRAFT.

A Facebook Reel goes live through three phases against the Page's video_reels edge: start an upload session, send the file to Meta's rupload host, then finish with a video_state of PUBLISHED, SCHEDULED or DRAFT. A reel that fails at finish is often a spec miss, so check the file before phase one. The checks below can be run against Sume media with an unbilled probe.
The three phases
| Phase | Call | Notes |
|---|---|---|
| Start | POST /page-id/video_reels with upload_phase: start | Initializes the session; returns a video_id and an upload_url |
| Upload | Transfer the file to the rupload.facebook.com endpoint | POST the file to the upload_url from start; this call carries no upload_phase |
| Finish | POST /page-id/video_reels with upload_phase: finish | video_state: PUBLISHED, SCHEDULED or DRAFT |
Spec to check before phase one
| Item | Value |
|---|---|
| Duration | 3 to 90 seconds |
| Aspect ratio | 9 x 16 |
| Resolution | 1080 x 1920 recommended; 540 x 960 minimum |
| Frame rate | 24 to 60 fps |
| File type | .mp4 recommended |
| Codec | H.264, H.265 (VP9, AV1 also supported) |
| Audio | AAC Low Complexity, stereo, 48 kHz, 128 kbps or more |
Preflight with Sume
Probe the clip once, then gate the three calls on the result. The probe's duration_seconds, width, height and fps map to the first four rows. The check below uses those fields.
def reel_problems(p):
w, h = p.get("width") or 0, p.get("height") or 0
out = []
if not 3 <= (p.get("duration_seconds") or 0) <= 90:
out.append("duration outside 3-90 s")
if w * 16 != h * 9:
out.append("not 9:16")
if min(w, h) < 540 or max(w, h) < 960:
out.append("below 540x960")
if not 24 <= (p.get("fps") or 0) <= 60:
out.append("fps outside 24-60")
return out
print(reel_problems({"duration_seconds": 30, "width": 1080,
"height": 1920, "fps": 30}))
If it fails the check
Video trim shortens a clip ($0.02 per job; duration 0.2 to 900 seconds) and, in exact mode, conforms size and rate with output (fps 24, 25, 30 or 60). To go from a 16:9 source to 1080x1920 with a blurred background, Timeline 1.0 renders 1080x1920 by default with fit: "blur" per slot, at $0.10 per output minute.
Limits
The page names no maximum file size. It lists 3 to 90 seconds; that is the only source we read, so confirm the length against a draft before relying on it for a campaign. Use DRAFT while you test: it lets you inspect the result without publishing.
Sources
Related posts
More in Developers
- Fix one sentence in an AI avatar video without a full re-render
ElevenLabs can regenerate only edited dubbing regions. A Sume avatar video is one job, so the fix is to keep clips short and join them: the cost math.
- FLUX API 402, 403 and 503 errors, and Sume's equivalents
BFL returns 402 for credits, 403 for key permission, 503 for load. Sume returns 402 insufficient_credits and 503 provider_capacity_exceeded. What to do.
- Gemini API video upload limits vs how Sume takes media inputs
Gemini accepts video inline under 100 MB or through the File API up to 20 GB paid and 2 GB free. How Sume's video routes take URLs, and where they refuse.
- Gemini prefixItems tuple schema: Sume rejects it, use an object
Gemini lists prefixItems for tuple-like arrays. Sume's output_schema allowlist omits it and returns unsupported_keyword; model each slot as a named property.
Written by Sume