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
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.
| Field | Type | Description |
|---|---|---|
post_id | string | The post being measured. |
metrics_by_account[] | object[] | Latest per-target snapshot — one row per published target. Fields are listed below. |
aggregated_metrics | object | Sums 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:
| Field | Type | Description |
|---|---|---|
target_id | string | The post target (one per connected account) this snapshot belongs to. |
platform | string | Platform identifier, e.g. x, instagram, linkedin. |
impressions | number | null | Times the post was served. |
reach | number | null | Unique accounts that saw the post. |
likes | number | null | Likes / reactions / favorites. |
comments | number | null | Comments or replies. |
shares | number | null | Shares / reposts / retweets. |
saves | number | null | Saves / bookmarks. |
clicks | number | null | Link or media clicks. |
video_views | number | null | Video views (0 for non-video posts). |
engagement_rate | number | null | Engagement rate as a fraction (e.g. 0.041 = 4.1%). |
captured_at | string (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 https://api.postfuze.com/api/v1/posts/p1a2b3c4-0000-4a9f-9b1c-2e7d6f0a1234/analytics \
-H "Authorization: Bearer sk_live_…"{
"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.
| Platform | Reported dimensions | Notes |
|---|---|---|
| X (Twitter) | impressions, reach, likes, comments, shares, clicks, video_views | Saves are not exposed on most access tiers. |
| impressions, reach, likes, comments, shares, clicks, video_views | Member-post analytics; requires the post-analytics scope. | |
| impressions, reach, likes, comments, shares, saves, video_views | Insights for Business/Creator accounts; clicks are not reported for feed posts. | |
| TikTok | impressions (views), likes, comments, shares, video_views | Reach is not exposed; impressions track video views. |
| impressions, reach, likes, comments, shares, clicks, video_views | Page-post insights. | |
| Threads | impressions (views), likes, comments, shares | Threads insights; reach/clicks not exposed. |
| Bluesky | likes, comments (replies), shares (reposts) | The AT Protocol has no impressions/reach surface. |
| YouTube | impressions, likes, comments, video_views | Views and engagement from the Data API. |
| impressions, saves, clicks | Pin metrics; "saves" are repins. | |
| Google Business | — | No post-level metrics — Google deprecated local-post insights, so Google Business targets never produce a snapshot. |
| impressions, likes, comments | Upvotes 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.
| Field | Type | Description |
|---|---|---|
social_account_id | string | The connected account. |
network | string | Platform identifier. |
source | string | live or snapshot. |
followers | number | null | Follower / subscriber count. |
following | number | null | Accounts this account follows. |
posts_count | number | null | Total posts on the platform. |
impressions | number | null | Account-level impressions over the platform's reporting window. |
reach | number | null | Account-level reach over the reporting window. |
profile_views | number | null | Profile views over the reporting window. |
raw | object | The unmapped platform payload for this snapshot. |
captured_at | string (ISO 8601) | When this snapshot was collected. |
curl https://api.postfuze.com/api/v1/social-accounts/a1b2c3d4-0000-4a9f-9b1c-2e7d6f0a1234/metrics \
-H "Authorization: Bearer sk_live_…"{
"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" }
}{
"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.
| Field | Type | Description |
|---|---|---|
id | string | The connected account. |
network | string | Platform identifier. |
healthy | boolean | true when the probe (or fallback check) succeeded. |
status | string | Account status, re-read after the probe, e.g. active, token_expired, disabled. |
error_code | string | null | Classified failure reason when not healthy (e.g. token_expired), else null. |
token_expires_at | string (ISO 8601) | null | When the stored OAuth token expires, if known. |
checked_live | boolean | Whether a live platform probe ran (vs. a status-only check). |
checked_at | string (ISO 8601) | When this check ran. |
curl https://api.postfuze.com/api/v1/social-accounts/a1b2c3d4-0000-4a9f-9b1c-2e7d6f0a1234/health \
-H "Authorization: Bearer sk_live_…"{
"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
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.