---
title: "Analytics"
description: "Read likes, reposts, replies, quotes and X views for your published posts through the Octopost REST API or the MCP get_analytics tool."
---

# Analytics

Octopost reads the numbers for your published posts back from each network, so you (or your agent) can see what worked: likes, reposts, replies, quotes and, on X, views. There's nothing to set up. Once a post is live, Octopost starts reading its numbers about an hour later and keeps them up to date for 30 days.

## What each network reports

| Network | Likes | Reposts | Replies | Quotes | Views | Extra |
|---------|-------|---------|---------|--------|-------|-------|
| X | Yes | Yes | Yes | Yes | Yes | Bookmarks |
| Bluesky | Yes | Yes | Yes | Yes | No | |
| Mastodon | Yes (favourites) | Yes (boosts) | Yes | Servers that count them | No | |
| Threads | Not available yet | | | | | |
| LinkedIn | Not available | | | | | |

Networks without analytics never cause an error. Their posts just have no numbers, and the response says why.

## Good to know

- **When numbers update.** Octopost keeps three snapshots for every post: about 1 hour, 24 hours and 7 days after it went live, so you can compare posts at the same age. On Bluesky and Mastodon the latest numbers also refresh through the day (hourly on day one, then every few hours). On X the latest numbers update at the snapshots and when you ask for a refresh.
- **Threads of posts.** Numbers are for the first post of the thread.
- **30 days.** After 30 days a post's numbers stop updating. They stay readable.
- **Engagement** is likes + reposts + replies + quotes. Views aren't part of it.
- **Deleted posts.** If you delete a post on the network, Octopost stops reading it and its numbers are marked unavailable.

## Summary

```
GET /api/analytics
```

Totals per network and your top posts for posts published in a date range.

| Query | Description |
|-------|-------------|
| `from` | Start, ISO date or date-time. Default: 30 days before `to`. |
| `to` | End. Default: now. |
| `platform` | `twitter`, `bluesky` or `mastodon`: only count that network. |
| `top` | How many top posts to list, 0 to 50. Default 5. |

```bash
curl "https://octopost.ink/api/analytics?from=2026-11-01&top=3" \
  -H "Authorization: Bearer $OCTOPOST_API_KEY"
```

```json
{
  "success": true,
  "from": "2026-11-01T00:00:00.000Z",
  "to": "2026-11-04T18:00:00.000Z",
  "postsMeasured": 6,
  "totals": { "likes": 212, "reposts": 31, "replies": 18, "quotes": 4, "impressions": 15400, "engagement": 265 },
  "byNetwork": {
    "twitter": { "likes": 180, "reposts": 27, "replies": 15, "quotes": 4, "impressions": 15400, "engagement": 226, "posts": 4 },
    "bluesky": { "likes": 32, "reposts": 4, "replies": 3, "quotes": 0, "impressions": null, "engagement": 39, "posts": 2 }
  },
  "topPosts": [
    {
      "postId": "3f1c...",
      "content": "We shipped dark mode.",
      "publishedAt": "2026-11-02T15:00:12.000Z",
      "totals": { "likes": 120, "reposts": 20, "replies": 9, "quotes": 3, "impressions": 9800, "engagement": 152 },
      "networks": { "twitter": { "likes": 120, "reposts": 20, "replies": 9, "quotes": 3, "impressions": 9800, "engagement": 152 } }
    }
  ],
  "lastUpdated": "2026-11-04T17:49:03.000Z",
  "support": { "twitter": { "available": true, "fields": ["likes", "reposts", "replies", "quotes", "impressions", "bookmarks"] } }
}
```

## One post

```
GET /api/analytics/{postId}
```

Per network: the latest numbers (with when they were read), the snapshots taken so far, and the post's URL.

```json
{
  "success": true,
  "postId": "3f1c...",
  "publishedAt": "2026-11-02T15:00:12.000Z",
  "tracked": true,
  "totals": { "likes": 120, "reposts": 20, "replies": 9, "quotes": 3, "impressions": 9800, "engagement": 152 },
  "networks": {
    "twitter": {
      "available": true,
      "url": "https://twitter.com/i/web/status/1853...",
      "latest": { "likes": 120, "reposts": 20, "replies": 9, "quotes": 3, "impressions": 9800, "engagement": 152, "bookmarks": 11, "fetchedAt": "2026-11-03T15:04:00.000Z" },
      "snapshots": {
        "1h": { "likes": 14, "reposts": 2, "replies": 1, "quotes": 0, "impressions": 900, "engagement": 17, "fetchedAt": "2026-11-02T16:04:00.000Z" },
        "24h": { "likes": 120, "reposts": 20, "replies": 9, "quotes": 3, "impressions": 9800, "engagement": 152, "fetchedAt": "2026-11-03T15:04:00.000Z" }
      }
    },
    "threads": { "available": false, "note": "Not available yet: Threads needs an extra permission Octopost does not ask for yet." }
  }
}
```

## Several posts at once

```
GET /api/analytics?post_ids=ID1,ID2,ID3
```

The latest numbers for up to 200 posts, keyed by post id. Posts with no numbers yet are left out.

```json
{
  "success": true,
  "posts": {
    "ID1": {
      "totals": { "likes": 9, "reposts": 1, "replies": 2, "quotes": 0, "impressions": 640, "engagement": 12 },
      "networks": { "twitter": { "likes": 9, "reposts": 1, "replies": 2, "quotes": 0, "impressions": 640, "engagement": 12, "fetchedAt": "2026-11-04T12:04:00.000Z" } }
    }
  }
}
```

## Refresh now

```
POST /api/analytics/refresh
```

Reads fresh numbers for your posts from the last 30 days, or for one post with `{"postId": "..."}`. Posts read in the last 15 minutes are skipped. You can refresh once every 10 minutes; sooner returns `429` with `Retry-After`. You rarely need this, since Octopost refreshes on its own.

```json
{ "success": true, "read": 12, "skipped": 3, "failed": 0, "rateLimited": 0 }
```

`rateLimited` counts posts skipped because a network asked Octopost to slow down. Those are read on a later pass.

## From an agent (MCP)

The `get_analytics` tool does the same thing:

- `get_analytics` with `post_id`: one post's numbers and snapshots.
- `get_analytics` without `post_id`: the summary, with optional `from`, `to`, `platform` and `top`.
- Add `refresh: true` to read fresh numbers first.

A typical loop: "Which of last week's posts did best? Write three more like it and queue them for my approval." The agent calls `get_analytics` with `from` set to a week ago, reads `topPosts`, then calls `queue_post` with `needs_approval: true`.

## Things Octopost handles for you

You don't need to worry about these, but in case you're curious: Octopost reads X with your connected account's own sign-in (refreshing it when needed), Bluesky through its public API and Mastodon through your own server, batches reads to stay well inside each network's rate limits, and backs off automatically when a network asks it to slow down.
