API reference · Analytics
Get cross-platform analytics
GET /v1/analytics
Read private saved reports for all collected destinations, without a 50-post sample or live provider calls. Cold or invalidated reports use a globally limited read-only builder; busy admission returns 429 with Retry-After: 60 and database failures return 503. Same-filter saved results can be retained with snapshot.stale=true. measuredAt is collection time and snapshot.generatedAt is preparation time. Production reads check the durable generation directly, without an additional edge-cache delay after collection completes. Period mode reports observed counter growth, including older content; Content mode reports lifetime performance of content published in the range. Totals, current, chart and breakdown share mode and filters. Rankings contain up to twenty destinations. POST /v1/analytics/refresh queues collection; GET that route reports progress. GET /v1/analytics/methodology defines reconciliation and freshness.
Request
Authenticate with a bearer API key. Agents calling over MCP authorise with OAuth instead and never handle the key.
curl -X GET https://api.postlake.dev/v1/analytics \
-H "Authorization: Bearer $POSTLAKE_API_KEY"Parameters
| Field | Type | Required | What it is |
|---|---|---|---|
period | string | No | Query string. |
profile | string | No | Query string. Profile username. Omit for the whole workspace. |
mode | string | No | Query string. Observed period activity, or lifetime performance of content published in the range. |
sort | string | No | Query string. Rank the complete matching destination population before limiting to 20. Unavailable readings sort last. Rates require a positive reported audience count. |
channel | string | No | Query string. Connected social account ID. Shared posts contribute only matching destinations. |
network | string | No | Query string. Optional platform name. |
from | string | No | Query string. Inclusive first UTC date, YYYY-MM-DD. |
to | string | No | Query string. Inclusive last UTC date, YYYY-MM-DD. Maximum range 365 days. |
agent | string | No | Query string. Stable creator client ID, not display name. Unknown historical attribution is not inferred. |
origin | string | No | Query string. Recorded interface, separate from creator identity. Account-wide supplements are omitted for actor/interface filters. |
Response
Success (200).
| Field | Type | Required | What it is |
|---|---|---|---|
period | string | Yes | |
since | string | Yes | |
mode | string (period, content) | No | |
until | string | No | |
measuredAt | string | No | |
snapshot | object | No | Private saved report metadata. generatedAt is report preparation, not metric collection. stale means rebuilding failed or a newer collection invalidated this report. |
snapshot.generatedAt | string | Yes | |
snapshot.stale | boolean | Yes | |
rankingScope | string | No | |
rankingMetric | string (views, likes, comments, engagement, engagementRate, impressions, clicks) | No | |
averageViews | object | No | Content mode only: lifetime views divided by destinations explicitly reporting views. Includes measured zeros, excludes unknown readings. A shared post counts once per destination. |
averageViews.value | number | Yes | |
averageViews.destinations | integer | Yes | |
nativeExtrasBasis | string | No | Supplemental native extras are lifetime counters, never period activity. |
posts | integer | Yes | Distinct posts with collected readings published in the range, not every published post. |
totals | object | Yes | |
methodologyUrl | string | No | Machine-readable metric and reconciliation definitions. |
current | object | No | Selected-mode totals for the requested window. Period: observed activity. Content: lifetime results of published content. |
previous | object | No | Same mode and filters in the preceding equal-length window. Null means no usable observations. |
timeseries | array of object | No | UTC daily observed growth in Period mode, or lifetime results by publication date in Content mode. Includes likes, comments and engagementRate. A daily rate is null without usable audience counts, not zero. |
byChannel | array of object | No | One row per social account with readings. Optional connected distinguishes live connections from retained disconnected history; disconnected accounts can have available post metrics. Same verified identity/destination in the same profile reuses its channel ID on reconnection. Native profile insights in account are separate from post totals. Optional accountPeriod gives days (30) and capturedAt for the independent rolling profile window; followers is current or null. Threads account.views means profile views. Profile-only rows have posts=0, available=false and empty reports for post metrics. Native readings are omitted with custom dates, agent or origin filters. fieldCoverage describes returned post counters, not historical daily coverage. |
topPosts | array of object | No | Up to 20 ranked destinations across the complete matching population, with optional thumbnailUrl. A shared post can appear more than once. |
derived | object | No | Engagement, blended engagement rate and CTR. See methodology for denominators. |
byPlatform | array of object | Yes |
Example
{
"period": "…",
"since": "2026-08-16T07:00:00Z",
"mode": "period",
"until": "…",
"measuredAt": "2026-08-16T07:00:00Z",
"snapshot": {
"generatedAt": "2026-08-16T07:00:00Z",
"stale": false
},
"rankingScope": "…",
"rankingMetric": "views"
}Limits
Reads are limited to 600 requests a minute per account. Every response carries X-RateLimit-Remaining and X-RateLimit-Reset, and a 429 includes Retry-After so a client can back off without guessing. Reads of network data (comments, followers, conversations) are metered in credits; reads of your own PostLake state are not.
Calling this from an agent
Agents do not call REST. They call tools. Over the hosted MCP server this operation is get_analytics, and the agent authorises with OAuth so your API key is never pasted into a chat or seen by the model. The reply comes back in the same normalised shape you see above, which is what lets an agent reason about it without a per-network branch.
You control what any one agent may do with it: which brands it may act for, which networks it may reach, how much it may do in a day, and whether it may spend credits you bought up front. See the MCP server.
Errors
Every failure uses one shape: a stable type, a granular code, the field at fault in param, and a plain-language fix. An agent can act on it without a person reading the message. See Errors and retries.
Idempotency-Key.Common questions
Can an AI agent do this without the REST API?
Yes. Over MCP the same job is the get_analytics tool. The agent authorises with OAuth, so no API key is pasted into a chat or seen by the model, and the answer comes back in the same normalised shape.
How often can I call this?
Reads are rate limited per account and every response carries X-RateLimit-Remaining and X-RateLimit-Reset. On a 429 the Retry-After header tells you how long to wait.
What happens when it fails?
You get one error shape: a stable type, a granular code you can branch on, the offending field in param, and a fix written so an agent can correct itself rather than stop. Full list on Errors and retries.