Engineering

Facebook Reels Insights API: views, watch time and retention

A Facebook Reel and a Facebook feed video return different metric sets, and the one most people reach for first - a completion rate - does not exist for Reels at all. Every field Meta reports for a Reel, how to read the 41-point retention graph, and how to tell an unreported metric from a measured zero.

A Facebook Reel and a Facebook feed video are the same object as far as publishing is concerned, and two different objects as far as analytics are concerned. They do not return the same metrics. The one most people reach for first — a completion rate — exists for feed video and does not exist for Reels at all, because Meta does not report one.

That asymmetry is the whole reason this is confusing. So rather than a tour of the API, here is the metric set for each video type, what Meta actually gives you, and what you have to compute or live without.

If you are looking for how to post a Reel rather than measure one, that is on the Facebook API page, which covers the upload, the cover image and the specs. This post is only about reading the numbers back.

What Facebook returns for a Reel

One call, GET /v1/posts/{id}/analytics, returns metrics per social account the post went to, plus aggregated totals. For a Facebook Reel the video-specific fields are:

Field

What it means

views

Plays excluding replays

total_plays

Plays including replays

replays

Replay count

average_watch_time_ms

Average time spent watching, in milliseconds

total_watch_time_ms

Total watch time across all plays, replays included, in milliseconds

followers_gained

Page followers gained from this Reel

retention

The retention graph — opt in, see below

Plus the engagement fields every post has: likes, comments, shares, impressions, reach, engagement_total, engagement_rate.

Two things to internalise from that table. views and total_plays are not the same number, and the gap between them is your replay volume — which, for a Reel, is often the more interesting figure. And watch time is in milliseconds, not seconds; an average_watch_time_ms of 4200 is 4.2 seconds, and treating it as 4,200 seconds is the kind of bug that makes a dashboard look plausible and be wrong by three orders of magnitude.

media_product_type tells you which kind of object you are holding. It is one of FEED, REELS or STORY, and you should branch on it rather than inferring the type from which fields came back non-null.

The metric Reels do not have

approx_completion_rate is Facebook regular video only — the share of views that reached 97% of the video. It is approximate because Meta has no true completion metric, and for Reels it has none at all. So the field is simply absent from a Reel's metric set rather than returned as zero.

If you need a completion-like number for a Reel, the honest substitutes are average_watch_time_ms against the Reel's duration, or the last point of the retention graph, which roughly approximates the completion share. Both are estimates. Neither is a completion rate, and labelling one as such in a client-facing report is how you end up defending a number you cannot source.

The retention graph

Retention is the part nothing else on our site covers, and it is opt-in because it costs an extra Graph API request per video:

bash
1curl "https://api.outstand.so/v1/posts/POST_ID/analytics?include=retention" \
2  -H "Authorization: Bearer YOUR_API_KEY"

include is a comma-separated list of costlier metrics. retention is ignored for other networks and for non-video posts, so you can pass it unconditionally in a mixed-network report without special-casing.

What comes back under metrics.retention is an array of at most 41 points:

json
1"retention": [
2  { "t": 0,  "pct": 100 },
3  { "t": 1,  "pct": 86.4 },
4  { "t": 2,  "pct": 71.9 },
5  { "t": 40, "pct": 18.2 }
6]

t is a position key returned by Meta, ascending — an interval index over the video, not a timestamp in seconds. pct is the percentage of plays still watching at that position, 0–100. Meta samples the points; you do not choose the resolution, and a short Reel will come back with fewer than 41.

Reading it: the drop between t: 0 and t: 1 is your hook, and it is usually the steepest segment in the array. The last point roughly approximates the completion share. A shelf — a long run of near-identical pct values — means people who got that far mostly finished. The whole array is null when Meta did not report it, which is not the same as a flat zero.

Reels vs feed video vs Stories

Metric

Reel

Feed video

Instagram Story

views

plays excluding replays

yes

yes

total_plays / replays

yes

—

—

average_watch_time_ms

yes

yes

—

total_watch_time_ms

yes

yes

—

approx_completion_rate

no — Meta reports none

yes

—

followers_gained

yes

—

—

retention

with include=retention

with include=retention

—

likes / comments

yes

yes

not supported, returns null

replies / profile_visits / follows / link_clicks

—

—

yes

Instagram Stories are in that table because they are the sharpest edge case: their insights are only queryable for roughly 24 hours after publish. After that the endpoint returns metrics: null with metrics_error.code: "metrics_expired" for that account. If a Stories number matters to you, you have to collect it on the day.

Instagram Reels are a fourth shape again — they add skip_rate, the share of views skipped within the first three seconds, which has no Facebook equivalent. Meta marks it as estimated and in development, so treat it as directional.

What is not available, and how you can tell

This is the section worth reading before you design a schema, because the API distinguishes three states that are easy to collapse into one.

  • A measured zero stays 0. Nobody watched it is a fact, and it is reported as a fact.
  • An unreported metric is null, and it is named in platform_specific.unavailable_fields. That list is the thing to log. null on its own does not tell you whether Meta refused the metric, does not support it for this object type, or has not computed it yet — the named list is how you find out which fields were actually asked for and did not come back.
  • engagement_total and engagement_rate are null when the counts they derive from are unavailable rather than being computed from a partial set. Aggregated totals across accounts sum only available measurements, so a total can be partial; engagement-rate averages include measured zeros and exclude unavailable rates.

Meta's own raw metric names are under platform_specific.video, alongside the video id — useful when you need to reconcile a figure against Business Suite. The names you will see there include post_video_avg_time_watched for Facebook and ig_reels_avg_watch_time, ig_reels_video_view_total_time and reels_skip_rate on the Instagram side.

Two operational constraints that are not metric gaps but will look like them:

  • Reading video insights needs the read_insights permission, and the connected Page user needs the Analyze task. Without it you get no video fields, and the failure is a permission problem rather than an empty Reel.
  • Instagram data can lag up to 48 hours. A Reel published this morning with thin numbers this afternoon is usually not a measurement bug.

Why the Graph API route is more work than it looks

You can of course read all of this from Meta directly. What you take on by doing so: insights live on versioned edges, so a Graph API version bump can change or retire a metric under you; the video fields come from a different edge than the engagement counts, so one post is several requests; the retention graph is another request again; and Meta's deprecations land as runtime errors rather than warnings. We hit exactly that — fetching Facebook Stories and Reels analytics started failing with a "singular statuses API is deprecated" error, which we had to absorb and repair on our side.

The reason to use one endpoint instead is not that the Graph API is hard to call. It is that the mapping — which metric exists for which media_product_type, which name Meta uses this version, which absence is a null and which is a zero — is the part that rots, and it is better maintained in one place than in your reporting code.

Common questions

Can I get Facebook Reels views via the API? Yes — views on GET /v1/posts/{id}/analytics, counting plays excluding replays. Use total_plays if you want replays included.

Does the Facebook API return watch time? Yes, as average_watch_time_ms and total_watch_time_ms, both in milliseconds, with total watch time including replays.

What is the Facebook Reels insights endpoint? Through Outstand it is GET /v1/posts/{id}/analytics, with ?include=retention for the retention graph. There is no separate Reels-only route; you branch on media_product_type.

Is there a completion rate for Reels? No. Meta reports none. approx_completion_rate is feed video only — use average watch time or the last retention point, and label it as an estimate.

Does this work for Reels I did not publish through Outstand? Yes. Facebook video and Reel insights work for imported posts as well as ones published through the API, and Reels are included in Facebook Page post imports.

Where to go next

The post analytics reference documents every field above with its per-network behaviour, the Facebook configuration guide has the Page permissions and connection requirements, and the social media analytics API guide covers the same endpoint across every network we support rather than just Facebook video.

If the reason you want these numbers is a report somebody else reads, exportable social media reports is the shorter path — the retention graph and watch-time fields are the ones worth putting in front of a client, and the unavailable_fields list is what keeps a report honest about what it could not measure.