Docs

Analytics

Analytics and normalized metrics for Bluesky posts with likes, comments, reposts, quotes, and bookmarks.

Read performance metrics for a published Bluesky post. Bluesky exposes interaction counts only. There are no impressions, reach or view data. Pass post as described in Analytics. Use the internal id for Onepostly posts. Use the native post URI plus accountId for outside posts.

MetricAvailable
Likes
Comments
Shares (reposts plus quotes)
Saves (bookmarks)
Engagement
Impressions-
Reach-
Plays-

Request

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 });
console.log(analytics.subjects);

Response

{
  "analytics": {
    "post": "POST_ID",
    "status": "ready",
    "fetchedAt": "2026-08-25T10:00:00.000Z",
    "subjects": [
      {
        "scope": "destination",
        "subjectId": "DEST_ID",
        "accountId": "ACCOUNT_ID",
        "platform": "bluesky",
        "externalPostId": "at://did:plc:xyz/app.bsky.feed.post/abc123",
        "status": "ready",
        "metrics": {
          "impressions": null,
          "reach": null,
          "likes": 40,
          "comments": 8,
          "shares": 3,
          "saves": 1,
          "plays": null,
          "engagement": 52
        },
        "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.

Outside posts

Posts published outside Onepostly work the same way once they are synced. Sync by native post id then read analytics with that 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, postId: "at://did:plc:xyz/app.bsky.feed.post/abc123" },
});
if (!found || !post) throw new Error("Post not found on the account.");

const { analytics } = await analyticsApi.getAnalytics({
  post: post.externalPostId,
  accountId,
});

Normalized fields

FieldMeaning on Bluesky
impressionsAlways null. Bluesky has no impressions API
reachAlways null
likesLike count
commentsReply count
sharesReposts plus quotes
savesBookmark count
playsAlways null
engagementLikes plus comments plus shares plus saves

Missing values stay null.

Account stats

Live follower, following and post counts for a connected account.

const stats = await connections.getConnectionStats({ id: accountId });
// stats.stats.followerCount, followingCount, videoCount (post count)

Errors

CodeCauseFix
NOT_PUBLISHEDDestination not published yetWait for publish
ANALYTICS_PERMISSIONSession invalidReconnect with a new App Password
ANALYTICS_FAILEDHard failureRetry. Contact support if it persists

See also