Api Analytics

Analytics

Every route here is arithmetic over posts we have already stored. None of them calls a model, none of them costs anything, and all of them work with a read-only key.

GET /analytics/summary/

Best and worst performing posts, best and worst by engagement, total likes, post count, and when the history was last refreshed or analysed.

GET /analytics/best-time-to-post/

Ranked posting slots, best first:

{"status": "success", "data": {
  "handle": "alice.bsky.social",
  "weeks": 1,
  "slots": [{"slot": "Tuesday 14:00", "score": 0.93, "post_count": 4}]
}}
Parameter Default Notes
weeks 1 How far back to look, 1–52
limit 10 How many slots, 1–50
detail top full returns the whole grid with the posts in each slot

The parameter is detail, not formatformat is reserved for content negotiation and would return a 404.

Slots are bucketed in the account's own timezone, the same one GET /me/ reports, so this matches what you see in the web app.

GET /analytics/posts/

The stored post history, newest first.

Parameter Default Notes
limit 50 Capped at 100
offset 0
order posted_at or engagement, or likes

Each post carries uri, text, posted_at, likes, reposts, replies, quotes and engagement (the sum of the last four). The response includes pagination with total, limit and offset.

GET /analytics/insights/

The stored profile insight blob, or {} if nothing has been generated yet. This never triggers generation — if it is empty, the message tells you which route would fill it.

POST /analytics/sync/

Refreshes the post history from Bluesky. Write scope, because it makes outbound calls on your behalf.

There is a fifteen-minute freshness window; send {"force": true} to bypass it. Analytics read whatever was last synced, so call this first if you want up-to-the-minute numbers.