# 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](https://pozy.ai/docs/analytics/post-analytics) |
| Accounts | Account insights, follower history, demographics, channel and page totals | [Platform insights](https://pozy.ai/docs/analytics/platform-insights) |
| Timing | Best hour of the week to post, posting frequency against engagement | [Best time to post](https://pozy.ai/docs/analytics/best-time) |
| Lifespan | How fast a post collects its engagement after publishing | [Content decay](https://pozy.ai/docs/analytics/content-decay) |
| Verified tracking | A watched window per post, raw samples and an authenticity score | [Tracking and authenticity](https://pozy.ai/docs/analytics/tracking) |
| Inbox | Message volume, response times, busiest hours, top accounts | [Inbox analytics](https://pozy.ai/docs/analytics/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.

```bash cURL
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"
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/analytics/dashboard?profileId=6650c1e2a3b4c5d6e7f80911&fromDate=2026-10-01&toDate=2026-10-29&compare=previous_period", {
  headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);

console.log(data.totals.impressions, data.previousTotals?.impressions);
```
```python Python
import os
import requests

resp = requests.get(
    "https://api.pozy.ai/v1/analytics/dashboard",
    headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
    params={
        "profileId": "6650c1e2a3b4c5d6e7f80911",
        "fromDate": "2026-10-01",
        "toDate": "2026-10-29",
        "compare": "previous_period",
    },
    timeout=30,
)
data = resp.json()
print(data["totals"]["impressions"])
```

```json
{
  "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`](https://pozy.ai/docs/api/syncExternalPosts) 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](https://pozy.ai/docs/publishing/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`](https://pozy.ai/docs/api/getAnalyticsDelta) 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`](https://pozy.ai/docs/api/getAnalytics) 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](https://pozy.ai/docs/webhooks/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](https://pozy.ai/docs/connect/account-health) |
| `429` | You hit the workspace [rate limit](https://pozy.ai/docs/getting-started/rate-limits). Wait for `Retry-After`, then retry the same page |

## Endpoints

| Endpoint | What it does |
| --- | --- |
| [`GET /v1/analytics/dashboard`](https://pozy.ai/docs/api/getAnalyticsDashboard) | Get an analytics dashboard |
| [`GET /v1/analytics`](https://pozy.ai/docs/api/getAnalytics) | Get post analytics |
| [`GET /v1/analytics/delta`](https://pozy.ai/docs/api/getAnalyticsDelta) | Analytics changed since a cursor |
| [`GET /v1/analytics/daily-metrics`](https://pozy.ai/docs/api/getDailyMetrics) | Get daily aggregated metrics |
| [`GET /v1/accounts/follower-stats`](https://pozy.ai/docs/api/getFollowerStats) | Get follower stats |
| [`GET /v1/analytics/post-timeline`](https://pozy.ai/docs/api/getPostTimeline) | Get post analytics timeline |
| [`POST /v1/posts/sync-external`](https://pozy.ai/docs/api/syncExternalPosts) | Sync an external post |

## Webhook events

| Event | When it fires |
| --- | --- |
| [`analytics.synced`](https://pozy.ai/docs/webhooks/events#analytics-synced) | Analytics synced. |
| [`post.tracking_completed`](https://pozy.ai/docs/webhooks/events#post-tracking-completed) | Post tracking completed. |
| [`post.authenticity_changed`](https://pozy.ai/docs/webhooks/events#post-authenticity-changed) | Post authenticity changed. |
