Analytics

Get top posts

Cached top posts with full metrics and time-limited media URLs, sortable including outlier sorts that surface LinkedIn posts that over-performed the account's own baseline. Workspace-wide lists mix LinkedIn and X posts. For an X thread the metrics cover its first tweet only, not the whole thread.

GET/api/public/analytics/top-posts
cURLGET /api/public/analytics/top-posts
curl "https://postiv.ai/api/public/analytics/top-posts?accountId=all&limit=10&sort=date" \
  -H "Authorization: Bearer pk_postiv_..."
200Response
{
  "success": true,
  "posts": [
    {
      "id": "ap1...",
      "hook": "I lost my biggest client...",
      "postUrl": "https://linkedin.com/...",
      "mediaType": "text",
      "isLeadMagnet": false,
      "mediaUrls": [],
      "metrics": {
        "impressions": 12400,
        "engagements": 640,
        "engagementRate": 5.2
      },
      "outliers": {
        "engagementMultiplier": 2.1
      }
    }
  ]
}

Authorization

Requires an organization API key with the analytics:read scope, sent as Authorization: Bearer or x-api-key.

Query parameters

accountIduuid|'all'required

Alias: account_id. One LinkedIn profile/page or X account from list-analytics-accounts, or 'all' for a workspace-wide list that mixes channels.

channelstring

Narrows accountId 'all' to one channel. With a specific accountId it must match that account's channel (400 channel_mismatch).

One of:linkedinx
limitintdefault: 25

1-100.

sortstringdefault: "date"

Outlier sorts are LinkedIn only: 400 unsupported_sort when only X is in scope, and in a mixed list LinkedIn posts rank first. X posts support date, impressions and engagements.

One of:dateengagementsimpressionsengagement_outlierreach_outliertype_outliertype_reach_outlier
periodint

Trailing days, 1-365. Default is all-time (no filter) if omitted and no start/end.

startstring
endstring

Response

Top posts with metrics and outlier scores.

posts[].iduuid
posts[].platformstring

The post's channel.

posts[].accountIduuid

Workspace-wide lists only: the analytics account the post belongs to.

posts[].hook / postUrl / linkedinUrn / createdAt / mediaType / category / isLeadMagnet / lastFetchedAtmixed
posts[].xPostId / syncStatusmixed

X only: the tweet id and where its stored metrics stand in the periodic X sync.

posts[].mediaUrlsstring[]

Refreshed signed URLs. Always empty for X: X post images are Postiv assets, read them with GET /api/public/posts/:id.

posts[].metricsobject

{ impressions, engagements, engagementRate, reactions, comments, reshares, membersReached, postSaves, postSends, linkClicks, premiumCtaClicks, followersGainedFromContent, profileViewsFromContent, videoPlays, videoWatchTime, videoViewers } for LinkedIn. For X: impressions, engagements, engagementRate, linkClicks and profileClicks, with reactions = likes, comments = replies, reshares = reposts, plus quotes and bookmarks.

posts[].outliersobject

LinkedIn only: { weightedEngagementScore, impressionsMultiplier, engagementMultiplier, mediaTypeImpressionsMultiplier, mediaTypeEngagementMultiplier, calculatedAt, overallBaselineSampleSize, mediaTypeBaselineSampleSize }

posts[].metadataobject

{ postText, postLink }

Errors

400missing_account_id
400invalid_channel / channel_mismatch: channel is not linkedin or x, or contradicts the channel of accountId
400unsupported_sort: an outlier sort when only X posts are in scope; sort X by date, impressions or engagements
404analytics_account_not_found

Good to know

  • Rows with no analytics id AND all-null impressions/engagements (queue-only, never imported) are filtered out: this only ranks posts that actually have analytics.