# Analytics · PostLake Docs > Markdown version of https://docs.postlake.dev/analytics . The canonical page for humans. > PostLake is the social media API for AI agents: https://postlake.dev/llms.txt Read how your posts performed, per post, or across every network at once, in **one normalised response**. Same metric names everywhere, so you (or an agent) can compare platforms without special-casing each one. ## Per-post analytics Fetch the metrics for a single post across all the accounts it went to: ``` curl https://api.postlake.dev/v1/posts/post_a1b2c3/analytics \ -H "Authorization: Bearer YOUR_API_KEY" ``` The response includes `measuredAt` and one `targets[]` entry per destination. Each target has `reports[]`, the metric names its adapter can expose, and `metrics`. A `null` metrics value means PostLake could not read that target's insights; it does not mean the post had zero engagement. In the dashboard, open a published post to see these figures beside its content. That view refreshes while open and also has a manual Refresh button. Networks may update their counts later than PostLake checks them. ## Cross-platform analytics Roll up performance across every connected account over a period (this is the differentiator. One call, every network): ``` curl "https://api.postlake.dev/v1/analytics?period=30d" \ -H "Authorization: Bearer YOUR_API_KEY" ``` Reports cover all collected publishing destinations, without a recent-50-post sample or live provider calls. Totals, charts, comparisons and breakdowns use the same mode and filters. - `mode=period` (default): positive counter growth observed during the selected UTC dates, including older content. - `mode=content`: latest lifetime counters for destinations published during those dates, not activity received in that period. - `period=7d`, `30d` or `90d`: inclusive UTC calendar-day windows ending today. - `from=2026-09-01&to=2026-09-30`: inclusive UTC dates, maximum 365 days. - `profile`: profile username, using its current channels. `channel`: social account ID. `network`: platform name. - `agent`: stable creator client ID. `origin`: recorded interface (`dashboard`, `api` or `agent`). Historical attribution is not guessed. Account-wide supplements are omitted with either identity filter. Shared posts contribute only matching destinations. Moving a channel changes current profile attribution. `posts` counts distinct tracked posts with collected readings published in the range, not every published post. `topPosts` ranks up to 20 destinations across the complete matching population; one shared post can appear more than once. Rankings do not cap totals. ## Refresh without waiting ### Selected metrics and rankings Use `sort=views`, `likes`, `comments`, `engagement`, `engagementRate`, `impressions` or `clicks`. The default is `engagement`. Sorting happens across the complete matching population before the 20-destination limit, not just the already-returned rows. `rankingMetric` confirms the chosen sort. Missing readings rank last. Dashboard metric cards select the chart and ranking together. Clicks and impressions remain under More metrics when supported. Count bars show a channel or network's share of the selected reported metric. Rate bars compare actual percentages with the highest available rate; percentages are never added into a contribution total. A daily rate uses that day's eligible activity or content results, not an average of post percentages. Gaps mean no usable audience, not zero engagement. In Content performance, `averageViews` contains `value` and `destinations`: lifetime views divided by destinations explicitly returning views, including reported zeros. Each channel of a shared post counts separately. Unknown readings are excluded. Period performance returns `null`; new activity divided by newly published posts would be misleading. `POST /v1/analytics/refresh` queues collection and returns immediately. `GET /v1/analytics/refresh` reads queued, running, complete or failed, with posts checked and errors. Active jobs are reused; new starts have a five-minute cooldown. Complete does not mean every provider returned every field. Previous valid readings survive provider failures. Due work is checked every 15 minutes. Content up to seven days old is eligible every 15 minutes, 8-28 days hourly, 29-90 days daily, and older content weekly. Refresh respects eligibility and provider cooldowns. Reports have a 15-second cache, invalidated by job progress. The dashboard updates during collection without losing your filters. Provider reporting can lag. MCP: call `get_analytics` with the same filters and `refresh: true`, then call again for progress. SDK: `client.analytics.get()`, `client.analytics.refresh()` and `client.analytics.refreshStatus()`. ## Engagement rate and metric definitions Views are the network's reported view counter. Impressions count displays where a network exposes that metric. Neither is a unique-person count, and a video view is not interchangeable with an impression. PostLake does not estimate impressions from views or interactions. **Engagement** is likes or reactions + comments or replies + shares, reposts or quotes + saves + clicks, using the counters available from each network. Missing counters are not measured zero. Clicks are not necessarily outbound link clicks. One person can perform several actions. **Per-network engagement rate** is engagement divided by the audience counter, multiplied by 100. PostLake uses views when reported, otherwise impressions. The denominator is not followers or reach. Without a positive usable audience count, the rate is unavailable, not zero. **Across networks**, PostLake divides the summed eligible engagement by the summed audience counts, then multiplies by 100. It does not average network percentages. Networks with no usable audience count contribute to neither side of the rate, although their measured engagement can still appear in the engagement total. Eligibility is checked per destination, even when other posts on that channel report an audience. A reported zero views count does not fall back to impressions. Rates use the latest reported-field coverage; unknown legacy readings use adapter capabilities as a fallback. Daily rates use the same eligible numerator and denominator rules. For example, 100 actions on 1,000 TikTok views and 10 actions on 200 Pinterest impressions give (100 + 10) / (1,000 + 200) × 100 = 9.2%, rounded to one decimal. This is a blended action rate across different measurement rules, not a unique-person rate or the native rate shown by either platform. Multiple actions per exposure can produce rates above 100%. The selected mode applies to both sides: Period mode uses observed activity increments; Content mode uses lifetime readings for content published in the chosen dates. Different networks expose different actions, so compare trends within a network as well as the blended headline. Account-wide insights are never added to post totals. The dashboard's metric information controls explain these definitions. Post Detail keeps network-specific impressions separate and labels unavailable readings N/A. A reported zero remains 0. API clients and agents can read the same rules through `GET /v1/analytics/methodology`. ## Reconciliation methodology - Identity includes workspace, PostLake post, connected channel and remote object ID. Channels on the same network do not collide. - A first reading within 24 hours of publication counts from zero; a later first reading establishes a baseline. - Period activity counts growth above the previous highest counter. Downward corrections stay visible in lifetime readings; recovery is not counted twice. - Growth across gaps belongs to its observation date. Missing event dates and unidentified counter resets cannot be reconstructed. - Previous periods use adjacent equal-length UTC date windows with the same filters. Today can be incomplete. Null means no usable observations, not zero. - Account insights in `byChannel.account` are separate. Native extras are supplemental lifetime counters, labelled `nativeExtrasBasis: latest_lifetime`, not period activity. - Raw evidence retains the latest daily observation. Atomic updates maintain rollups; duplicate timestamps and older readings cannot inflate them. - Legacy readings migrate only when one destination is identifiable. Ambiguous evidence remains retained, not multiplied. Call `GET /v1/analytics/methodology` for machine-readable definitions. ## The metric shape Every platform maps to the same `Metrics` object, so a chart or an agent reads one set of fields regardless of source: | Field | Meaning | | --- | --- | | `impressions` | Times the post was shown. | | `views` | Views or watches, where reported. | | `reach` | Unique accounts that saw it. | | `likes` | Likes / reactions. | | `comments` | Comments / replies. | | `shares` | Reposts / shares. | | `saves` | Saves / bookmarks. | | `clicks` | Link / profile clicks. | | `followers` | Follower count / growth for the account. | Not every network exposes every metric. Target `reports[]` lists adapter capabilities; `metrics.reported` lists counters actually returned by that reading, including measured zero. An absent field is not measured zero. Incomplete readings preserve earlier valid lifetime values. Older evidence without `reported` has unknown coverage. Channel `fieldCoverage` counts fields present in latest observations, scoped by observation date in Period mode or publication date in Content mode, not historical daily coverage. Channel metadata and avatar bytes refresh in bounded background work and preserve previous valid images on failures. See the [analytics methodology](https://postlake.dev/api/get-analytics-methodology) for machine-readable reconciliation rules. **Why an agent cares.** Because the response is normalised, an assistant can ask "which of last week's posts did best, and on which network?" and reason over one consistent shape. Then decide what to post next.