Analytics

PostFuze surfaces two kinds of performance data: post analytics (per-target engagement for a single published post) and account metrics & health (audience size and connection status for a connected account). These endpoints fetch live from each platform on request — they snapshot the result into the post_analytics / account_metrics tables and return the freshly captured numbers. A background job also writes periodic snapshots, which serve as the fallback when a live pull fails or a date range is requested.

Live, with a snapshot fallback

Post analytics and (un-ranged) account metrics fetch live and persist a snapshot; each row carries a captured_at timestamp. If a live pull fails for a given account, that account is skipped and the others are still returned. Account metrics with a since/until range serve the stored snapshot series instead (see Availability & latency).

Post analytics

GET /posts/{id}/analytics

Fetches live engagement for each published target, snapshots it, and returns the latest snapshot per target plus aggregated totals across every target of the post. Returns 404 if the post does not exist or has been deleted. For a draft or scheduled post — where nothing has been published yet — it returns 422 Unprocessable Entity (“Analytics are only available after a post is published”) rather than empty arrays.

FieldTypeDescription
post_idstringThe post being measured.
metrics_by_account[]object[]Latest per-target snapshot — one row per published target. Fields are listed below.
aggregated_metricsobjectSums across all targets: total_impressions, total_reach, total_likes, total_comments, total_shares, total_saves, total_clicks, total_video_views.

Each entry in metrics_by_account[] is the most recent snapshot for one target:

FieldTypeDescription
target_idstringThe post target (one per connected account) this snapshot belongs to.
platformstringPlatform identifier, e.g. x, instagram, linkedin.
impressionsnumber | nullTimes the post was served.
reachnumber | nullUnique accounts that saw the post.
likesnumber | nullLikes / reactions / favorites.
commentsnumber | nullComments or replies.
sharesnumber | nullShares / reposts / retweets.
savesnumber | nullSaves / bookmarks.
clicksnumber | nullLink or media clicks.
video_viewsnumber | nullVideo views (0 for non-video posts).
engagement_ratenumber | nullEngagement rate as a fraction (e.g. 0.041 = 4.1%).
captured_atstring (ISO 8601)When this snapshot was collected.

Metrics are null when a platform does not report that dimension (for example, X does not expose saves for most access tiers). Aggregated totals coerce nulls to zero. Each metrics_by_account[] row maps to a target on the post, so you can join it back to targets[].id for the live permalink and status.

curl
curl https://api.postfuze.com/api/v1/posts/p1a2b3c4-0000-4a9f-9b1c-2e7d6f0a1234/analytics \
  -H "Authorization: Bearer sk_live_…"
200 OK
{
  "post_id": "p1a2b3c4-0000-4a9f-9b1c-2e7d6f0a1234",
  "metrics_by_account": [
    {
      "target_id": "t_0001",
      "platform": "x",
      "impressions": 12480,
      "reach": 9820,
      "likes": 421,
      "comments": 38,
      "shares": 57,
      "saves": null,
      "clicks": 304,
      "video_views": 0,
      "engagement_rate": 0.041,
      "captured_at": "2026-06-10T18:00:00.000Z"
    },
    {
      "target_id": "t_0002",
      "platform": "instagram",
      "impressions": 30410,
      "reach": 24890,
      "likes": 1894,
      "comments": 132,
      "shares": 410,
      "saves": 765,
      "clicks": 0,
      "video_views": 18220,
      "engagement_rate": 0.102,
      "captured_at": "2026-06-10T18:00:00.000Z"
    }
  ],
  "aggregated_metrics": {
    "total_impressions": 42890,
    "total_reach": 34710,
    "total_likes": 2315,
    "total_comments": 170,
    "total_shares": 467,
    "total_saves": 765,
    "total_clicks": 304,
    "total_video_views": 18220
  }
}

What's available per platform

Snapshots store a common set of columns, but which dimensions a platform actually reports varies. The matrix below is a guide — any dimension a platform omits is returned as null. The raw, unmapped platform payload is retained server-side for each snapshot, so new dimensions can be surfaced without a migration.

PlatformReported dimensionsNotes
X (Twitter)impressions, reach, likes, comments, shares, clicks, video_viewsSaves are not exposed on most access tiers.
LinkedInimpressions, reach, likes, comments, shares, clicks, video_viewsMember-post analytics; requires the post-analytics scope.
Instagramimpressions, reach, likes, comments, shares, saves, video_viewsInsights for Business/Creator accounts; clicks are not reported for feed posts.
TikTokimpressions (views), likes, comments, shares, video_viewsReach is not exposed; impressions track video views.
Facebookimpressions, reach, likes, comments, shares, clicks, video_viewsPage-post insights.
Threadsimpressions (views), likes, comments, sharesThreads insights; reach/clicks not exposed.
Blueskylikes, comments (replies), shares (reposts)The AT Protocol has no impressions/reach surface.
YouTubeimpressions, likes, comments, video_viewsViews and engagement from the Data API.
Pinterestimpressions, saves, clicksPin metrics; "saves" are repins.
Google BusinessNo post-level metrics — Google deprecated local-post insights, so Google Business targets never produce a snapshot.
Redditimpressions, likes, commentsUpvotes are reported as likes; impressions come from view_count, which Reddit exposes only on some posts.

Account metrics

GET /social-accounts/{id}/metrics

With no date range, prefers a live pull from the platform (source: "live") and falls back to the most recent stored snapshot (source: "snapshot") when the platform is unreachable. If nothing exists either, the response is { social_account_id, metrics: null, message }. Pass since/until (unix seconds or ISO 8601) to get a data array of stored snapshots over the window instead — see Social Accounts for the ranged shape.

FieldTypeDescription
social_account_idstringThe connected account.
networkstringPlatform identifier.
sourcestringlive or snapshot.
followersnumber | nullFollower / subscriber count.
followingnumber | nullAccounts this account follows.
posts_countnumber | nullTotal posts on the platform.
impressionsnumber | nullAccount-level impressions over the platform's reporting window.
reachnumber | nullAccount-level reach over the reporting window.
profile_viewsnumber | nullProfile views over the reporting window.
rawobjectThe unmapped platform payload for this snapshot.
captured_atstring (ISO 8601)When this snapshot was collected.
curl
curl https://api.postfuze.com/api/v1/social-accounts/a1b2c3d4-0000-4a9f-9b1c-2e7d6f0a1234/metrics \
  -H "Authorization: Bearer sk_live_…"
200 OK
{
  "social_account_id": "a1b2c3d4-0000-4a9f-9b1c-2e7d6f0a1234",
  "network": "instagram",
  "source": "live",
  "captured_at": "2026-06-14T12:10:00.000Z",
  "followers": 48210,
  "following": 312,
  "posts_count": 1893,
  "raw": { "period": "day" }
}
200 OK — no metrics yet
{
  "social_account_id": "a1b2c3d4-0000-4a9f-9b1c-2e7d6f0a1234",
  "metrics": null,
  "message": "No metrics have been collected for this account yet."
}

Account health

GET /social-accounts/{id}/health

A connection check. When the platform adapter supports it and the account isn’t disabled/revoked, it runs a live probe (refreshing the token if near expiry, then a lightweight identity call) so a hard 401 is caught even when the stored status looks fine; checked_live reports whether that probe ran. Otherwise it falls back to a status + token-expiry check. A refresh failure during the probe flips the account to token_expired (and fires account.token_expired). Returns 404 if the account does not exist or has been deleted.

FieldTypeDescription
idstringThe connected account.
networkstringPlatform identifier.
healthybooleantrue when the probe (or fallback check) succeeded.
statusstringAccount status, re-read after the probe, e.g. active, token_expired, disabled.
error_codestring | nullClassified failure reason when not healthy (e.g. token_expired), else null.
token_expires_atstring (ISO 8601) | nullWhen the stored OAuth token expires, if known.
checked_livebooleanWhether a live platform probe ran (vs. a status-only check).
checked_atstring (ISO 8601)When this check ran.
curl
curl https://api.postfuze.com/api/v1/social-accounts/a1b2c3d4-0000-4a9f-9b1c-2e7d6f0a1234/health \
  -H "Authorization: Bearer sk_live_…"
200 OK
{
  "id": "a1b2c3d4-0000-4a9f-9b1c-2e7d6f0a1234",
  "network": "instagram",
  "healthy": true,
  "status": "active",
  "error_code": null,
  "token_expires_at": "2026-08-08T06:00:00.000Z",
  "checked_live": true,
  "checked_at": "2026-06-14T12:30:00.000Z"
}

Availability & latency

These endpoints fetch live on request and snapshot the result. A background job also writes periodic rows to post_analytics / account_metrics, which back the snapshot fallback (and the account-metrics date-range series).

Platform data still lags publishing

A live pull only returns what the platform itself reports — and most platforms surface engagement on a delay and aggregate to whole-day windows, so figures shortly after publishing may be sparse or zero. For a draft or scheduled post, GET /posts/{id}/analytics returns 422 (nothing is published yet); once published it returns the live/snapshotted numbers. When an account has never been measured, GET /social-accounts/{id}/metrics returns metrics: null. Treat every figure as a point-in-time capture and compare captured_at values to measure change over time.

For programmatic reactions to publishing outcomes (rather than polling analytics), subscribe to webhooks such as post.published. See the per-platform guides for which insights each network exposes.