Docs

Analytics Freshness

How fresh Onepostly metrics are and when to read them again.

How collection works

Metrics are collected by a background pipeline. It refreshes every published subject on a schedule that depends on the age of the post. You never trigger a collection. The analytics endpoint always returns the latest stored reading.

Freshness schedule

Post ageRefresh cadence
Younger than 30 daysAbout every hour
30 days or olderAbout once a week, for the life of the post

Reach and impressions can carry an additional platform side delay. See the platform Analytics page for details.

The post selector

Every comments, engagement and analytics call targets one value called post. Pass an internal Onepostly post id to cover all its destinations at once. Pass a platform native post id to target a single post that lives on the platform. Native ids require accountId. Unknown native ids sync on first read. destinationId is internal only. Use it to narrow one destination inside an internal post.

In short, use the internal id when the post was created through Onepostly. Use the native id plus accountId when the post was published outside Onepostly. All pages below follow this rule. They link back here instead of repeating it.

Reading the data

Every subject carries fetchedAt with the UTC time of the latest reading. Compare it with your freshness tolerance before showing the numbers in your product.

Polling more often than the schedule does not return fresher data. A GET never triggers a background refresh. Newer numbers appear only after the pipeline completes its next scheduled pass.

import { Configuration, AnalyticsApi } from "@onepostly/sdk";

const analyticsApi = new AnalyticsApi(
  new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY }),
);

const { analytics } = await analyticsApi.getAnalytics({ post: postId });
for (const subject of analytics.subjects ?? []) {
  console.log(subject.platform, subject.status, subject.metrics);
}
{
  "analytics": {
    "post": "POST_ID",
    "status": "ready",
    "fetchedAt": "2026-08-25T10:00:00.000Z",
    "subjects": [
      {
        "scope": "destination",
        "subjectId": "DEST_ID",
        "accountId": "ACCOUNT_ID",
        "platform": "instagram",
        "externalPostId": "17895695668004550",
        "status": "ready",
        "metrics": {
          "impressions": 4200,
          "reach": 3100,
          "likes": 180,
          "comments": 12,
          "shares": 9,
          "saves": 44,
          "plays": 4200,
          "engagement": 245
        },
        "fetchedAt": "2026-08-25T10:00:00.000Z",
        "expiresAt": "2026-08-25T11:00:00.000Z",
        "error": null
      }
    ]
  }
}

Daily timeline

Day by day cumulative metrics per subject. Optional from and to bound the inclusive UTC date range. The default window covers the last 366 days.

const { timeline } = await analyticsApi.getAnalyticsTimeline({
  post: postId,
  from: "2026-08-01",
  to: "2026-08-28",
});
// timeline.subjects[].points[].day, metrics, delta

Each metrics object holds cumulative totals as captured on that UTC day. delta compares them against the previous stored day. A delta entry stays null when either side has no value. Days without a stored snapshot have no data. The API does not backfill them.

Outside posts

Posts published outside Onepostly work the same way once they are synced. Sync by URL then read analytics with the native id plus accountId. Omit url and postId to refresh the recent posts of the account in bulk.

const { found, post } = await posts.syncExternal({
  syncExternalBody: { accountId, url: "https://x.com/you/status/1960956321831854280" },
});
if (!found || !post) throw new Error("Post not found on the account.");

const { analytics } = await analyticsApi.getAnalytics({
  post: post.externalPostId,
  accountId,
});
{
  "synced": {
    "postsFound": 1,
    "postsSynced": 1,
    "skipped": false
  },
  "found": true,
  "post": {
    "id": "EXT_ID",
    "accountId": "ACCOUNT_ID",
    "platform": "x",
    "externalPostId": "1960956321831854280",
    "externalUrl": "https://x.com/you/status/1960956321831854280",
    "mediaKind": "text",
    "publishedAt": null,
    "syncedAt": "2026-08-25T10:00:00.000Z",
    "analyticsStatus": "ready"
  }
}

Status values

A subject reports exactly one status.

StatusMeaning
readyMetrics were collected and are available
pendingNot collected yet. The subject was published but the pipeline has not produced a reading, or the platform has not made metrics available yet
failedThe last collection attempt failed. The error code and message are attached to the subject
unsupportedThe platform does not expose analytics for this type of subject
unavailableThe subject was never published

Metrics that a platform does not report stay null. A null value means the platform provides no number for that metric. It does not mean zero.

Notes

X subjects refresh live on each GET and bill the wallet like other X usage. See X Analytics.