Analytics

Analytics

Read reach, engagement, followers and inbox activity for every connected account, and keep your own copy fresh with a cursor feed and a webhook.

Pozy collects metrics from every connected account on a schedule and serves them through one set of endpoints. You get per-post numbers, account-level insights, follower history, timing recommendations and inbox activity, all filtered by the same profileId, accountId and platform parameters.

The dashboard's Analytics pages read these same endpoints, so a chart you see in the app is a request you can make yourself.

#What Pozy measures

Area What you get Start with
Posts Impressions, reach, likes, comments, shares, saves, clicks, views and platform extras for every post Post analytics
Accounts Account insights, follower history, demographics, channel and page totals Platform insights
Timing Best hour of the week to post, posting frequency against engagement Best time to post
Lifespan How fast a post collects its engagement after publishing Content decay
Verified tracking A watched window per post, raw samples and an authenticity score Tracking and authenticity
Inbox Message volume, response times, busiest hours, top accounts Inbox analytics

#One call for an overview screen

GET /v1/analytics/dashboard returns totals, the previous period for comparison, follower growth, a daily series, top posts and recent posts in a single response. It is the fastest way to fill an overview page.

curl "https://api.pozy.ai/v1/analytics/dashboard?profileId=6650c1e2a3b4c5d6e7f80911&fromDate=2026-10-01&toDate=2026-10-29&compare=previous_period" \
  -H "Authorization: Bearer $POZY_API_KEY"
Response (trimmed)
{
  "dateRange": { "fromDate": "2026-10-01", "toDate": "2026-10-29" },
  "totals": { "impressions": 48210, "reach": 31987, "likes": 2114, "comments": 302, "shares": 140, "saves": 211, "clicks": 590, "views": 22340, "engagementRate": 5.86 },
  "previousTotals": { "impressions": 40112, "reach": 27650, "likes": 1802, "comments": 251, "shares": 98, "saves": 180, "clicks": 470, "views": 19011, "engagementRate": 5.8 },
  "followers": { "current": 12840, "gained": 312, "byAccount": [{ "accountId": "6650c1e2a3b4c5d6e7f80a22", "platform": "instagram", "current": 12840, "gained": 312 }] },
  "daily": [{ "date": "2026-10-01", "impressions": 1630, "reach": 1210, "engagement": 96, "views": 702, "followersGained": 9 }],
  "topPosts": [],
  "recentPosts": [],
  "dataAsOf": "2026-10-29T22:40:00.000Z"
}
Parameter Meaning
fromDate, toDate Required. UTC days as YYYY-MM-DD, both inclusive, up to 366 days apart
profileId, platform Narrow to one customer profile or one platform. Both default to everything
compare=previous_period Adds previousTotals and previousFollowers for the window of the same length that ends the day before fromDate
topPosts, recentPosts How many posts to list, 0 to 25. Defaults are 5 and 10

#Two kinds of numbers

The dashboard mixes two views of the same data, and they answer different questions:

  • totals, previousTotals and daily count engagement that arrived during the window, on any post. A post from last month that gets 10 likes this week adds 10 to this week. totals always equals the sum of daily.
  • topPosts and recentPosts list posts published during the window, each with its lifetime metrics.

So the top posts do not add up to the totals, and they are not meant to. engagementRate is a percentage: likes, comments, shares and saves divided by impressions. Where a platform reports no impressions, Pozy falls back to reach, then views.

#Posts made outside Pozy

Every analytics endpoint takes source:

source Includes
all (default) Everything
pozy Posts you published through Pozy
external Posts published directly on the platform, picked up by a background sync

Pozy discovers external posts on each account about every 90 minutes. When someone pastes the URL of a post they published a minute ago, fetch it right away with POST /v1/posts/sync-external and the accountId plus url. That call is debounced per account, so use it for that moment, not as a bulk refresh. See External posts.

#How fresh the numbers are

Platforms release metrics on their own schedule. Show a "data as of" label instead of promising real time.

Data Typical freshness
Post likes and comments About an hour. A stale read triggers a background refresh
Post reach and impressions Platform dependent. About 24 hours on Instagram
Follower counts Once a day
External posts Discovered about every 90 minutes per account
YouTube daily views and channel totals 2 to 3 days behind
Google Business Profile performance 2 to 3 days behind

Use the freshness fields the API returns: dataAsOf on the dashboard, overview.lastSync and overview.dataStaleness on post lists, syncStatus per platform entry (synced, pending, unavailable) and analytics.lastUpdated per post. Show a spinner on pending rather than a zero.

#Keep your own copy in sync

If you store metrics in your own database, do not poll GET /v1/analytics once per account. Use the change feed:

#Take a cursor first

Call GET /v1/analytics/delta with no cursor. It returns an empty data array and the feed's current position in nextCursor.

#Load your baseline

Page through GET /v1/analytics once, at most 366 days per window.

#Resume from the cursor you took before the baseline

Call the delta feed with that cursor and keep sending back each nextCursor. hasMore: true means call again straight away. hasMore: false means you are caught up, so wait for your next interval.

Taking the cursor before the baseline matters. Changes that land while the baseline loads would otherwise be in neither. The overlap is safe because metrics are absolute values, not increments: a later row for a post replaces an earlier one.

Empty pages are not "no changes"

The feed holds back its last few seconds of writes. A read made the instant a change happens can come back empty. Poll again with the same cursor instead of advancing. The feed keeps 7 days of changes, and a cursor older than 6 days is rejected with a 400, so read at least once a day. If you fall behind, take a fresh cursor and reload the baseline.

Treat the cursor as opaque. Do not parse it, build it or sort on syncedAt, which can go backwards between entries.

#Get told when an account changes

Subscribe a webhook to analytics.synced. It fires once per account each time its analytics sync finishes, which is roughly hourly per account. The payload names the account and how many posts changed, and it carries no metrics: read the delta feed when it arrives.

On a busy workspace this event is high volume (1,000 connected accounts means about 1,000 events an hour). Give it its own webhook endpoint so a slow consumer cannot get your other events switched off, and return 2xx before you read the feed. See Retries.

The event is skipped when a sync fails, when the account was synced moments ago, when the account is disconnected, or when the platform has no analytics.

#Errors you may see

Status When
202 A single-post lookup (?postId=) whose sync is still running. Retry shortly
424 A single-post lookup where every platform failed to sync
409 An inactive account or one that needs reconnecting. Stop polling it until it is reconnected
429 You hit the workspace rate limit. Wait for Retry-After, then retry the same page

#Endpoints

Endpoint What it does
GET /v1/analytics/dashboard Get an analytics dashboard
GET /v1/analytics Get post analytics
GET /v1/analytics/delta Analytics changed since a cursor
GET /v1/analytics/daily-metrics Get daily aggregated metrics
GET /v1/accounts/follower-stats Get follower stats
GET /v1/analytics/post-timeline Get post analytics timeline
POST /v1/posts/sync-external Sync an external post

#Webhook events

Event When it fires
analytics.synced Analytics synced.
post.tracking_completed Post tracking completed.
post.authenticity_changed Post authenticity changed.