Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,16 +119,22 @@ The metric whitelist lives in `InstagramGraphAPI::Metrics`:
```ruby
InstagramGraphAPI::Metrics::MEDIA_INSIGHT_METRICS[:reel]
# => ["reach", "likes", "comments", "shares", "saved", "total_interactions",
# "plays", "views", "ig_reels_avg_watch_time", "ig_reels_video_view_total_time"]
# "views", "ig_reels_avg_watch_time", "ig_reels_video_view_total_time"]

InstagramGraphAPI::Metrics::ACCOUNT_INSIGHT_METRICS
# => ["views", "profile_views", "follower_count", "accounts_engaged",
# "total_interactions", "reach", "likes", "comments", "shares", "saves"]
```

Deprecated names (`impressions`, `engagement`, `video_views`) are
intentionally absent — the Graph v21 schema renamed/retired them in
2024–2025.
Deprecated names (`impressions`, `engagement`, `video_views`, `plays`,
`exits`) are intentionally absent — the Graph schema renamed or retired
them between 2024 and 2026.

Keep the list narrow. The API validates the whole `metric` list before it
reads any of it, so a single retired name answers 400 for the request and
costs every metric for that media kind. When one is rejected the error
names every value the API will accept, which is the list to widen
against.

### Comments + replies

Expand Down
31 changes: 23 additions & 8 deletions lib/instagram_graph_api/metrics.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
# frozen_string_literal: true

module InstagramGraphAPI
# Whitelist of insight metric names the Instagram Graph API (v21+)
# returns for each media kind. Names that Meta retired during the 2024
# and 2025 schema cleanups — `impressions`, `engagement`,
# `video_views` — are intentionally absent. `plays` is still supported
# for feed videos and reels in v21+ and is included accordingly.
# Whitelist of insight metric names the Instagram Graph API returns for
# each media kind. Names that Meta retired during the 2024 and 2025
# schema cleanups — `impressions`, `engagement`, `video_views`,
# `plays` — are intentionally absent.
#
# A rejected name fails the whole request, not just its own column: the
# API validates the metric list up front and answers 400 for all of it.
# One retired name therefore costs every metric for that media kind, so
# this list is only ever widened against a verified response.
#
# `plays` was dropped 2026-09-20. It had been kept on video and reel on
# the belief that v21 still served it; production rejects it with
# `metric[6] must be one of the following values: ...`, and that list
# does not contain it. Video takes `views` in its place, verified
# against a real feed video on the same call. The same error names
# every metric the API will accept, and `exits` is absent from it too,
# which is why the story set no longer asks for it.
#
# Consumed by the Rails ingestion layer in phase 5b.
module Metrics
MEDIA_INSIGHT_METRICS = {
image: %w[reach likes comments shares saved total_interactions].freeze,
video: %w[reach likes comments shares saved total_interactions plays].freeze,
reel: %w[reach likes comments shares saved total_interactions plays views ig_reels_avg_watch_time ig_reels_video_view_total_time].freeze,
story: %w[reach replies exits views].freeze,
video: %w[reach likes comments shares saved total_interactions views].freeze,
reel: %w[reach likes comments shares saved total_interactions views ig_reels_avg_watch_time ig_reels_video_view_total_time].freeze,
# `navigation` is what replaced `exits`, but it reports through a
# breakdown this client does not ask for yet, so it is left out
# until a live story can be used to check the shape that comes back.
story: %w[reach replies views].freeze,
carousel: %w[reach likes comments shares saved total_interactions views].freeze
}.freeze

Expand Down
24 changes: 16 additions & 8 deletions spec/instagram_graph_api/metrics_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,30 @@
end

it 'uses story-specific metrics' do
expect(described_class::MEDIA_INSIGHT_METRICS[:story]).to match_array(%w[reach replies exits views])
expect(described_class::MEDIA_INSIGHT_METRICS[:story]).to match_array(%w[reach replies views])
end

it 'excludes metrics deprecated in the 2024-2025 Graph schema' do
all_metrics = described_class::MEDIA_INSIGHT_METRICS.values.flatten.uniq
expect(all_metrics).not_to include('impressions') # retired across all media kinds
expect(all_metrics).not_to include('engagement') # retired
expect(all_metrics).not_to include('video_views') # renamed → `views`/`plays`
expect(all_metrics).not_to include('video_views') # renamed → `views`
end

it 'keeps `plays` on the kinds where Graph v21 still supports it' do
expect(described_class::MEDIA_INSIGHT_METRICS[:video]).to include('plays')
expect(described_class::MEDIA_INSIGHT_METRICS[:reel]).to include('plays')
%i[image story carousel].each do |kind|
expect(described_class::MEDIA_INSIGHT_METRICS[kind]).not_to include('plays')
end
# A retired name is not a missing column, it is a 400 for the whole
# request. While `plays` sat in the video list, every feed video and
# every reel published through the app recorded no insights at all,
# and the ingestion job logged the failure and moved on. `exits` was
# doing the same to stories.
it 'asks for no metric the API has retired out from under it' do
all_metrics = described_class::MEDIA_INSIGHT_METRICS.values.flatten.uniq
expect(all_metrics).not_to include('plays')
expect(all_metrics).not_to include('exits')
end

it 'counts video plays as `views`, the name that replaced them' do
expect(described_class::MEDIA_INSIGHT_METRICS[:video]).to include('views')
expect(described_class::MEDIA_INSIGHT_METRICS[:reel]).to include('views')
end
end

Expand Down
Loading