facebookvideo-encodinggraph-api

Facebook video specs aren't the problem. Your encode is.

··10 min read
Facebook video specs aren't the problem. Your encode is.

You looked up the pixel dimensions, exported at 1080x1920, and the Graph API still threw an error on publish. The table you found was right and it didn't help, because Facebook doesn't reject on dimensions. It rejects on the encode.

Quick answer: Facebook video specs come down to one container across every surface: MP4 with H.264 high profile, AAC Low Complexity stereo at 48 kHz and 128 kbps or higher, a fixed frame rate between 24 and 60 fps, closed GOP of 2 to 5 seconds, 4:2:0 chroma subsampling, and 1080x1920 for Reels and Stories or 1080x1350 for feed. You can hit that per file with ffmpeg -c:v libx264 -profile:v high -r 30 -g 60 -pix_fmt yuv420p -c:a aac -ar 48000 -b:a 128k -movflags +faststart and run your own encoder box, or send each file to the FFmpeg Micro API as one call and skip the encoder hosting, the queue, and the long-job babysitting.

Conventional wisdom says a platform spec is a resolution and a file size cap. Most spec pages produce exactly that, and for a human dragging one file into a browser it's enough. But the Graph API isn't checking your resolution first. It's checking frame rate constancy, GOP structure, audio codec, color transfer, and duration bounds, and it reports those failures with numeric codes that no dimension table lists.

The Facebook video specs for feed, Reels, and Stories

Facebook runs three publishing surfaces with three sets of numbers, and only the aspect ratio and duration genuinely differ between them. Everything below the aspect ratio is the same container.

SurfaceAspect ratioResolutionDurationFrame rate
Feed4:5 recommended, 16:9 through 9:16 accepted1080x1350 for 4:5Facebook's help pages still quote up to 240 minutes24 to 60 fps
Reels9:161080x1920 recommended, 540x960 minimum3 to 90 seconds24 to 60 fps
Stories9:161080x1920 recommended, 540x960 minimumup to 60 seconds for video24 to 60 fps

Meta's Reels publishing docs are the source for the Reels and Stories rows, and they add the parts the creator-marketing guides skip: H.264 or H.265 video, AAC Low Complexity audio at 48 kHz stereo and 128 kbps or more, closed GOP of 2 to 5 seconds, progressive scan, fixed frame rate, and 4:2:0 chroma subsampling. Meta's Page Stories documentation adds one rule that has nothing to do with encoding: a photo or video used in an already-published post can't be reused for a story.

The feed row deserves a caveat. Meta collapses more than 25 ad placements onto 4:5, 9:16, and 1:1, with 16:9 reserved for in-stream, so if your pipeline also feeds Ads Manager you're producing several crops of the same master anyway. That job is covered in Meta Ads video specs: all four ratios from one master file.

The encode preset that satisfies the publishing container

One FFmpeg command covers Reels and Stories, and swapping two numbers covers feed. The preset below pads rather than crops so nothing gets cut off, forces constant frame rate, and sets a closed 2-second GOP at 30 fps.

ffmpeg -i input.mov \
  -vf "scale=1080:1920:force_original_aspect_ratio=decrease,\
pad=1080:1920:(ow-iw)/2:(oh-ih)/2:color=black,fps=30" \
  -c:v libx264 -profile:v high -level 4.1 -preset medium -crf 20 \
  -maxrate 8M -bufsize 16M \
  -g 60 -keyint_min 60 -sc_threshold 0 -flags +cgop \
  -pix_fmt yuv420p \
  -c:a aac -profile:a aac_low -b:a 128k -ar 48000 -ac 2 \
  -movflags +faststart reel.mp4

For feed, change the filter chain to crop='min(iw,ih*4/5)':'min(ih,iw*5/4)',scale=1080:1350 and leave the rest alone. The -sc_threshold 0 -flags +cgop pair is the part people drop, and it's what turns x264's default scene-cut keyframes into the closed, evenly spaced GOP Meta asks for.

Before you hand anything to the API, gate it on a probe rather than on trust:

ffprobe -v error -select_streams v:0 \
  -show_entries stream=codec_name,width,height,r_frame_rate,avg_frame_rate,pix_fmt,color_transfer \
  -show_entries format=duration,size -of json input.mov

If r_frame_rate and avg_frame_rate disagree, the file is variable frame rate and will trip the frame-rate check. If color_transfer reads arib-std-b67 or smpte2084, the source is HDR, which is one of the documented causes behind opaque Meta container failures.

Normalizing a mixed batch before the publish step

A mixed batch is where this gets expensive. Take a faceless-channel operator pulling 40 clips a night from three sources: iPhone HEVC at variable frame rate, Veo renders at 24 fps, and stock footage at 1920x1080 with 5.1 audio. Every one of those needs the same normalize pass, and running it locally means one encoder machine, a queue, and someone watching for the job that hangs at 90 percent.

That's the step worth handing off. FFmpeg Micro takes the same normalize as one API call per file with no servers to run, returns a URL you can pass straight into the Graph API upload phase, and has a free tier to test the preset against your own worst input. If the only thing you need is the reframe, the Resize & Reformat blueprint does 9:16, 1:1, 4:5, and 16:9 with pad or crop as a one-click job.

Telling an upload failure apart from a spec violation

Facebook's Reels flow runs in phases: you start a session, upload the bytes, then call finish to publish. The bytes usually upload fine. The rejection lands on the finish call, which is why the failure looks like a publishing bug and is actually an encode bug. Meta documents four numeric codes for exactly this, and they're terminal: re-sending identical bytes reproduces them every time.

SignalCauseAction
HTTP 5xx or a dropped connection mid-transferTransport failureGET the upload session, read `file_offset`, resume the POST from that byte
Returned `file_offset` doesn't match what you sentA partial chunk landedResume from the returned offset instead of restarting
`1363040`Unsupported aspect ratioReframe to 9:16 and re-upload
`1363127`Unsupported resolutionScale to 1080x1920, never below 540x960
`1363128`Unsupported durationTrim into the 3 to 90 second window
`1363129`Unsupported frame rateForce CFR with `fps=30` between 24 and 60

The triage rule is short. A 1363xxx code means re-encode, so a retry queue that re-sends the same file is burning quota. Anything transport-shaped means resume from the offset Meta reports, because the Resumable Upload API exists precisely so you don't restart a 2 GB upload from zero.

Instagram's side of the same platform is worse about this. Its container returns 2207052, "Media upload has failed," with no field naming what broke, and the causes are the same encode-level ones. That error string has its own walkthrough in Fix Instagram Reels API error 2207052. Automation builders hit it often enough that the working answers live in Make Community threads rather than in Meta's docs.

Facebook video upload requirements you can't read off any table

Your account's real limits are an API field, not a published constant. Meta's Graph API reference defines a Video Upload Limits node with exactly two fields, length in seconds and size in bytes, which means the authoritative ceiling for the Page you're publishing to is something you read at runtime. Third-party guides quote 240 minutes and 4 GB, older API docs quoted 1.5 GB and 45 minutes for resumable uploads, and both can be true for different routes. Read the node, cache the answer, and validate against it.

Two encode details cause failures that look like nothing:

  • HEVC is accepted for Reels, but FFmpeg tags HEVC in MP4 as hev1 by default, and Apple's AVFoundation only decodes hvc1. Your file can publish and still show a broken thumbnail on iOS. Use -tag:v hvc1, or sidestep it entirely by encoding H.264 for anything headed to a social API.
  • Audio that isn't AAC-LC stereo at 48 kHz is a silent trap. A 44.1 kHz mono voiceover track satisfies every dimension table and still sits outside what Meta documents.

When one normalize pass is the wrong call

A single preset is the right answer when you're publishing to Facebook surfaces at volume and the source material varies. It's the wrong answer in two cases. If you're shipping a 90-minute feed upload, re-encoding the whole file to change a GOP setting wastes hours, and remuxing with -c copy plus -movflags +faststart is usually enough. And if your real need is animated templates with per-render text layout rather than format compliance, a template-editor service does that job better than any encode preset will.

FAQ

What are the Facebook Reels video size requirements?

Facebook Reels require a 9:16 vertical video at 1080x1920 recommended, with 540x960 as the documented minimum, running between 3 and 90 seconds at a fixed frame rate of 24 to 60 fps. Audio must be AAC Low Complexity, stereo, 48 kHz, at 128 kbps or higher.

Why does the Facebook Graph API reject a video that matches the published specs?

The Graph API rejects videos on encode properties that spec tables don't list: variable frame rate, open GOP, HDR color transfer, non-AAC audio, or a duration a fraction of a second outside the 3 to 90 range for Reels. Check r_frame_rate against avg_frame_rate and color_transfer with ffprobe before you blame the upload.

Should I use H.264 or H.265 for Facebook video uploads?

Use H.264 high profile for Facebook uploads. Meta's Reels docs accept H.264, H.265, VP9, and AV1, but H.264 avoids the hev1 versus hvc1 tagging problem that breaks HEVC playback and thumbnails on Apple devices, and it encodes faster in a batch pipeline.

Can one encode cover Facebook feed, Reels, and Stories?

One encode covers Reels and Stories, since both want 9:16 at 1080x1920 with the same codec and audio settings. Feed needs a separate pass because it wants 4:5 at 1080x1350, so a mixed publishing pipeline produces at least two renders per master file.

How do I normalize a batch of clips before publishing to Facebook?

Run every clip through the same scale, pad, CFR, and AAC pass before the upload phase, and probe the output rather than the input. Doing this per file locally needs an FFmpeg build and a worker to run it; doing it as an HTTP call means the same preset applies from n8n, Make, Zapier, or your own code with no encoder to maintain.

Normalize once, publish everywhere: sign up free, send your worst variable-frame-rate HDR clip through the preset above as one API call, and see whether the finish phase stops arguing with you. If you're wiring it into a workflow that waits on long renders, webhooks for long-running video jobs covers the polling side.

About Javid Jamae

Founder & CEO at FFmpeg Micro

Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.

Software EngineeringVideo ProcessingFFmpegCloud ArchitectureAPI DesignAutomation

Ready to process videos at scale?

Start using FFmpeg Micro's simple API today. No infrastructure required.

Get Started Free