API Status Get API Key

Post Metrics ⚠️ Experimental

Buffer collects performance data from the social networks it publishes to and exposes a normalized view of it through the API. This guide walks through how to read metrics for a single post, how to aggregate them across many posts, what the values mean across networks, and how to use them in common reporting workflows.

Required OAuth scope

Reading post metrics through OAuth requires the insights:read scope. It is separate from posts:read so you can grant access to performance data without granting access to post content management.

If you're authenticating with a personal API key, this section doesn't apply — your key already has full account access.

Include the scope alongside any others your app needs when redirecting the user to the authorization endpoint:

&scope=posts:read insights:read offline_access

See Authentication for the full OAuth flow and the list of available scopes.

Reading metrics for a single post

The Post type exposes two metrics-related fields:

  • metrics: [PostMetric!] — the list of metric values collected for the post.
  • metricsUpdatedAt: DateTime — the timestamp of the most recent metric refresh from the network.
query {
  post(input: { id: "your_post_id" }) {
    id
    text
    metrics {
      type
      name
      value
      unit
    }
    metricsUpdatedAt
  }
}

Each PostMetric carries:

  • type: PostMetricType! — the normalized identifier (e.g. reactions, impressions). Stable across networks. Use this when you're keying off metric values programmatically.
  • name: String! — a human-readable label (e.g. "Reactions", "Impressions"). Suitable for displaying in a UI without your own lookup table.
  • value: Float! — the numeric value.
  • unit: PostMetricUnit! — either count (integer-style values like impressions or reach) or percentage (e.g. engagementRate — values between 0 and 100).

See Get Post Metrics for the single-post pattern and Get Posts With Metrics for the paginated pattern.

Aggregated metrics

For reporting workflows that summarize a window of activity — quarterly recaps, channel-level rollups, BI exports — pulling every post and rolling values up client-side is expensive. The aggregatedPostMetrics query does the aggregation server-side and returns a single normalized result.

query {
  aggregatedPostMetrics(
    input: {
      organizationId: "your_organization_id"
      startDateTime: "2026-01-01T00:00:00Z"
      endDateTime: "2026-03-31T23:59:59Z"
      channelIds: ["your_channel_id"]
    }
  ) {
    metrics {
      type
      value
      unit
    }
    metricsUpdatedAt
  }
}

The result is an AggregatedPostMetrics value with two fields:

  • metrics: [PostMetric!]! — the aggregated metric values, in the same PostMetric shape returned per-post.
  • metricsUpdatedAt: DateTime — the latest metricsUpdatedAt across the matched posts. null when no posts matched the filter.

Filter input

AggregatedPostMetricsInput carries the aggregation window and any narrowing filters:

  • organizationId: OrganizationId! — the organization owning the channels.
  • startDateTime: DateTime! / endDateTime: DateTime! — the inclusive aggregation window. Typically UTC midnight on the first and last calendar days. The range is capped at 365 days; longer windows are rejected.
  • channelIds: [ChannelId!] — optional channel filter. Omit (or pass null) to aggregate across every channel in the organization the actor has insights access to. Passing an empty array matches no channels.
  • tags: TagComparator — optional tag filter. Omit to include all posts regardless of tags.

Baseline metrics and the postCount entry

Every successful response includes a baseline trio of entries: postCount, reactions, and comments. Posts on networks that don't track reactions or comments contribute 0 to those totals, so the values are always present and additive across the matched set.

postCount is a synthetic entry — it's the number of posts that matched the filter window, surfaced as a regular PostMetric with type: postCount and unit: count. It only appears on aggregate responses; the per-post Post.metrics field never emits it.

Cross-channel intersection

Beyond the baseline trio, the response includes additional metric types only when every channel in the filter set supports them. A single-network filter surfaces that network's richer metrics (e.g. impressions, reach, engagementRate on LinkedIn). A mixed-network filter trims the extras to the intersection — anything not reported by every network in the set is dropped from the result rather than zero-padded.

This is intentional: zero-padding a metric that one network doesn't track at all would misrepresent the aggregate. If you need a metric that's network-specific, narrow the filter to channels on a network that reports it.

See Aggregate Post Metrics for the canonical query shape and Get Quarterly Performance Report for a quarter-long window recipe.

Normalized metric names

Each social network reports performance data slightly differently. Buffer maps the underlying network metrics onto a single normalized enum (PostMetricType) so you can write one query that works across every channel. A few mappings to be aware of:

  • reactions — the count of positive reactions on the post. Instagram and Twitter likes, Mastodon favorites, and similar fields all normalize into reactions.
  • reposts — the count of shares-by-reposting. Twitter retweets, Mastodon reblogs, and Threads reposts all normalize into reposts.
  • comments — the count of replies and comments. Threads replies normalizes here.
  • shares — explicit share/forward actions (distinct from reposts, which is a repost-as-new-content action).
  • impressions, reach, views — view-count families. impressions may double-count repeat viewers; reach counts unique people; views is reported separately by networks that distinguish video views.

A handful of metrics are network-specific and only appear on posts from those networks:

  • saves — Instagram, Pinterest.
  • follows — Instagram (new followers attributed to the post).
  • quotes — Threads.
  • viewers — LinkedIn (unique video viewers).
  • totalTimeWatched — LinkedIn (total watch time in minutes).
  • likes — Facebook only. This is the Like-reaction subcount, distinct from reactions (which sums all Facebook reaction types: Like, Love, Care, Haha, Wow, Sad, Angry). Facebook's Graph API surfaces them separately and we preserve that.

The schema reference for PostMetricType lists every value with its per-network notes and is the source of truth — see the API reference for the full enum.

Data freshness

Metric values are pulled from each network on a daily cadence. Newly sent posts can therefore take up to ~24 hours before metrics first appear, and subsequent refreshes happen on the same daily rhythm.

Two implications worth designing around:

  • A missing metric does not mean zero. The metrics array on a single post only includes metric types that the network has actually reported for that post. If a metric is absent from the array, the network either hasn't surfaced it yet or doesn't support that metric for that post type. The non-null value: Float! means that when a metric is in the array, you can read it without null-checking. (On aggregatedPostMetrics, baseline entries are always emitted — missing-from-network posts contribute 0 to the sum.)
  • metricsUpdatedAt reflects the most recent ingestion, not the most recent network change. If a post's engagement spikes between two ingestion runs, you won't see the updated value until the next refresh. If your workflow is timing-sensitive, gate downstream actions on metricsUpdatedAt being recent enough rather than on the values themselves. On aggregatedPostMetrics, metricsUpdatedAt is the latest timestamp across the matched posts — i.e. as fresh as the most recently ingested post in the window.

Deprecated metric types to avoid

Seven PostMetricType values are deprecated and will be removed on 2026-07-31. They are not emitted by any current per-network definition and exist only for backwards compatibility with older clients. Migrate to the listed replacement:

Deprecated value Replacement
favorites reactions
retweets reposts
reblogs reposts
repins (no replacement — never emitted)
replies comments
link_clicks (no replacement — StartPage-only)
other (no replacement — never emitted)

GraphQL tooling will flag these values with deprecation warnings — treat those warnings as a signal to update your client.

Recipes

Quarterly performance report

Roll up a quarter of publishing activity into a single aggregate suitable for BI exports, board decks, or year-on-year comparisons. Pass a 90-ish-day window to aggregatedPostMetrics with the channels you care about and (optionally) a tag filter to scope the report to a campaign or content type.

See Get Quarterly Performance Report for the query shape. To compare quarters, run the query twice with different windows and diff the values client-side.

Next steps