# Post Metrics

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](authentication.html) 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.

```graphql
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](../examples/get-post-metrics.html) for the single-post pattern and [Get Posts With Metrics](../examples/get-posts-with-metrics.html) 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.

```graphql
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](../examples/aggregate-post-metrics.html) for the canonical query shape and [Get Quarterly Performance Report](../examples/get-quarterly-performance-report.html) 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](../examples/get-quarterly-performance-report.html) for the query shape. To compare quarters, run the query twice with different windows and diff the values client-side.

## Next steps

- [Authentication](authentication.html) — get your OAuth tokens with the `insights:read` scope.
- [Pagination](pagination.html) — page through large result sets when reading metrics across many posts.
- [Rate Limits](api-limits.html) — what to expect from the API when running large metrics queries.
- [Get Post Metrics](../examples/get-post-metrics.html) — the per-post query.
- [Get Posts With Metrics](../examples/get-posts-with-metrics.html) — the paginated query.
- [Aggregate Post Metrics](../examples/aggregate-post-metrics.html) — the cross-post aggregate query.
- [Get Quarterly Performance Report](../examples/get-quarterly-performance-report.html) — quarter-long window recipe.
