Analytics

Retrieve per-platform engagement metrics for your published posts. Backed by an age-tiered read-through cache with a hard 30-day refresh window; live across Instagram, Facebook, YouTube, TikTok, X, Threads, LinkedIn (for members who allow their post stats), Bluesky, and Pinterest (for business accounts). Telegram has none: it gives bots no numbers.

A workspace's analytics cover the accounts in it now, so moving an account to another workspace moves its numbers too, older posts included.

Post Analytics

Get per-platform analytics for a specific post. Only available for posts with status posted or partial. It lists the post's accounts that are in the workspace now; a post none of whose accounts are in the workspace any more answers 404 NOT_FOUND there.

GET/v1/analytics/post/{id}Get post analytics

Path Parameters

ParameterTypeDescription
idstringThe post ID

Example Request

Get analytics for a post
curl https://api.quegly.com/v1/analytics/post/post-abc123 \
  -H "Authorization: qgl_your_api_key"

Response

200 OK. Populated metrics for a published post
{
  "post_id": "post-abc123",
  "platforms": [
    {
      "platform": "instagram",
      "account_id": "acc-instagram-123",
      "post_url": "https://www.instagram.com/p/abc123",
      "metrics": {
        "views": 15420,
        "reach": 11290,
        "likes": 892,
        "comments": 47,
        "shares": 123,
        "saved": 234,
        "total_interactions": 1296
      },
      "fetched_at": "2026-06-02T10:00:00.000Z"
    },
    {
      "platform": "x",
      "account_id": "acc-x-345",
      "post_url": "https://x.com/user/status/123456",
      "metrics": {
        "views": 38400,
        "impressions": 38400,
        "likes": 412,
        "comments": 18,
        "retweets": 67,
        "quotes": 9,
        "bookmarks": 51,
        "url_link_clicks": 124,
        "user_profile_clicks": 22
      },
      "fetched_at": "2026-06-02T10:00:00.000Z"
    }
  ]
}

The metrics object is platform- specific. Keys vary by platform (TikTok exposes 4 keys, YouTube up to 10). See the table below for what each platform actually returns. Missing keys mean "the platform didn't expose that metric for this post," never zero.

Workspace Analytics (bulk)

Every published post in a date window with its per-platform metrics, in one call. Serves cached snapshots only; never hits platform APIs. Pair with the refresh endpoint below when freshness matters.

GET/v1/analyticsBulk workspace analytics

Query Parameters

ParameterTypeDescription
start_datestringISO date. Defaults to 30 days ago.
end_datestringISO date. Defaults to now.

Example Request

Pull a month of workspace analytics
curl "https://api.quegly.com/v1/analytics?start_date=2026-06-01&end_date=2026-06-30" \
  -H "Authorization: qgl_your_api_key"

Response

200 OK. One entry per published post, oldest first
{
  "workspace_id": "ws-123",
  "start_date": "2026-06-01T00:00:00.000Z",
  "end_date": "2026-06-30T23:59:59.000Z",
  "count": 1,
  "posts": [
    {
      "id": "post-abc123",
      "caption": "3 Ways to Prep Your Skin Before a Big Event",
      "status": "posted",
      "scheduled_at": "2026-06-28T13:00:00.000Z",
      "platforms": [
        {
          "platform": "instagram",
          "account_username": "myhandle",
          "post_url": "https://www.instagram.com/p/abc123",
          "metrics": { "views": 15420, "likes": 892, "comments": 47 },
          "captured_at": "2026-06-29T10:00:00.000Z"
        }
      ]
    }
  ]
}

metrics is empty and captured_at is null when nothing has been captured yet for that post+platform (distinct from a captured 0). The window may reach back past 30 days; posts older than the refresh window return their last (frozen) snapshot.

Refresh Analytics

Trigger an on-demand refresh sweep for the workspace. The scripted flow is: refresh, wait a few seconds, then GET /v1/analytics. Repeat calls within an hour re-fetch nothing, and posts older than 30 days are never re-fetched by any path.

POST/v1/analytics/refreshOn-demand analytics refresh

Example Request

Refresh workspace analytics
curl -X POST https://api.quegly.com/v1/analytics/refresh \
  -H "Authorization: qgl_your_api_key"

Response

200 OK. Sweep report
{
  "workspace_id": "ws-123",
  "attempted": 42,
  "refreshed": 40,
  "skipped": 108,
  "failed": 2
}

attempted = stale rows fetched this sweep; refreshed = fetched and stored; skipped = still fresh inside the 1-hour on-demand TTL (or LinkedIn's or Pinterest's slower cadence); failed = platform errors, picked up again on a later sweep.

Available Metrics by Platform

What we actually pull from each platform's analytics API. Keys map 1:1 to the metrics object in the response.

Instagram

From the post-March-2025 metric set on the Graph API media insights endpoint.views replaced the deprecated impressions.

ParameterTypeDescription
viewsnumberTotal views (Reels) or impressions (Feed/Carousel) post-March 2025.
reachnumberUnique accounts that saw the post.
likesnumberLike count.
commentsnumberComment count.
savednumberTimes the post was saved.
sharesnumberTimes the post was shared (DM, story re-share).
total_interactionsnumberAggregate of likes + comments + saves + shares (Meta-computed).
watch_time_secondsnumberReels only. Total seconds watched across all viewers.
avg_view_duration_secondsnumberReels only. Average seconds watched per view.

Facebook

Page Posts use the post-2026 post_views / post_clicks insights (the deprecated post_impressions family is sunset June 2026). Video Page Posts route through the Video node and expose a narrower set.

ParameterTypeDescription
viewsnumberpost_views for Page Posts; lifetime view count for Video nodes.
likesnumberReaction count (mirrors `reactions` for cross-platform parity).
reactionsnumberPage Posts only. Total reactions count summary.
commentsnumberComment count.
sharesnumberPage Posts only. Share count.
post_clicksnumberPage Posts only. Link/photo/video clicks for high-intent attribution.

YouTube

Two endpoints combined: Data API (basic stats, always granted) plus Analytics API (watch time + retention + subscriber attribution, requires yt-analytics.readonly scope. See Edge Cases).

ParameterTypeDescription
viewsnumberLifetime view count.
likesnumberLike count.
commentsnumberComment count.
favoritesnumberLegacy field, mostly 0 in modern data.
sharesnumberAnalytics API only. Share count for the lookback window.
watch_time_secondsnumberAnalytics API only. Total seconds watched (estimatedMinutesWatched × 60).
avg_view_duration_secondsnumberAnalytics API only. Average seconds watched per view.
avg_view_percentagenumberAnalytics API only. Percentage of the video watched on average (0 to 100).
subscribers_gainednumberAnalytics API only. New subscribers attributable to this video.
subscribers_lostnumberAnalytics API only. Subscribers lost in the same window.

TikTok

From the v2 video query endpoint. Requires the video.list scope (approved May 2026. See Edge Cases for older tokens).

ParameterTypeDescription
viewsnumberVideo view count.
likesnumberLike count.
commentsnumberComment count.
sharesnumberShare count.

X (Twitter)

public_metrics are always available; non_public_metrics (link clicks, profile clicks) are only returned within 30 days of the tweet's publish time. See Edge Cases.

ParameterTypeDescription
viewsnumberImpression count (mirrors `impressions` for cross-platform parity).
impressionsnumberSame value as `views`, kept distinct for clarity.
likesnumberLike count.
commentsnumberReply count (mapped to `comments` for cross-platform parity).
retweetsnumberRetweet count.
quotesnumberQuote count.
bookmarksnumberBookmark count.
url_link_clicksnumbernon_public_metrics. Clicks on links inside the tweet. 30-day window only.
user_profile_clicksnumbernon_public_metrics. Profile-visit clicks driven by the tweet. 30-day window only.

Threads

From the media insights endpoint. Threads exposes no reach, saves, or watch-time metric.

ParameterTypeDescription
viewsnumberView count.
likesnumberLike count.
commentsnumberReply count (mapped to `comments` for cross-platform parity).
repostsnumberRepost count.
quotesnumberQuote count.
sharesnumberShare count.

LinkedIn

From LinkedIn's member post statistics, which LinkedIn shares only with apps it approves for its Community Management API, and only for members who allow them (the r_member_postAnalytics permission, asked for when LinkedIn is connected). Until the member has allowed them, a LinkedIn entry's metrics is {} (see Edge Cases); its post_url links to the post either way.

ParameterTypeDescription
viewsnumberImpressions.
reachnumberUnique members reached.
likesnumberReaction count (mirrors `reactions` for cross-platform parity).
reactionsnumberTotal reactions, every kind.
commentsnumberComment count.
repostsnumberRepost count.
sharesnumberTimes the post was sent in LinkedIn messages.
savednumberTimes the post was saved.
url_link_clicksnumberClicks on links in the post.
watch_time_secondsnumberVideo posts only. Total seconds watched.

Bluesky

From Bluesky's public AppView (app.bsky.feed.getPosts). Bluesky's counts are public, so reading them needs no permission from the account. Bluesky exposes no views, reach, or watch-time metric.

ParameterTypeDescription
likesnumberLike count.
commentsnumberReply count (mapped to `comments` for cross-platform parity).
repostsnumberRepost count.
quotesnumberQuote count.
bookmarksnumberBookmark count.

Pinterest

From Pinterest's Pin analytics (GET /v5/pins/{id}/analytics), counted from the day the Pin was posted. Pinterest gives these numbers to business accounts only: a personal account's Pins read 0 on every key. They come in a day or two late, so Pinterest posts refresh on a slower cadence (see Caching & Refresh).

ParameterTypeDescription
viewsnumberImpressions.
likesnumberReactions (mapped to `likes` for cross-platform parity).
commentsnumberComment count.
savednumberTimes the Pin was saved.
post_clicksnumberClicks on the Pin.
url_link_clicksnumberClicks through to the Pin's link.

Telegram

None. Telegram gives bots no views, reactions or other numbers for a post, so a Telegram entry's metrics is always {}, and a refresh leaves Telegram posts out of its counts. This is not an error. Its post_url still links to the message.

Caching & Refresh

Analytics are served from a database cache, refreshed on a read-through pattern when the dashboard's Analytics page is opened or when you call POST /v1/analytics/refresh. Hitting the read endpoints repeatedly is cheap and won't cost extra platform-side reads.

PropertyValue
Cache TTLAge-tiered. Posts under 24h old refresh at most hourly, day-2 posts every 12 hours, day-3+ posts daily. POST /v1/analytics/refresh uses a flat 1-hour TTL for every post in the window instead. LinkedIn posts keep a slower cadence of their own in both, because LinkedIn limits its stats calls per day: at most every 6 hours on day 1, daily until day 7, then every 3 days. So do Pinterest posts, because Pinterest's numbers come in a day or two late: at most every 6 hours on days 1 and 2, then daily.
Refresh window30 days, hard. Posts older than 30 days keep their last captured snapshot forever; no path re-fetches them, dashboard or API (matches Instagram / X / Buffer / Hootsuite industry standard).
Staleness signalThe `fetched_at` / `captured_at` field on each platform entry tells you when that snapshot was last refreshed.
ConcurrencyUp to 8 platform fetches in flight per refresh sweep, with a 4 second per-fetch timeout so one slow platform can't block the others. LinkedIn posts are fetched one at a time, after the others.

For scripted pulls: POST /v1/analytics/refresh, wait a few seconds, then GET /v1/analytics. Calling refresh more than once an hour is a no-op, so there's no way to overspend platform reads by accident.

Edge Cases

Five real cases worth knowing about before you call this endpoint in production. Each one will show up as "empty metrics" or "missing fields" and is otherwise hard to debug from the response alone.

TikTok: brief delay between publish and analytics availability

When TikTok accepts a publish, we get back a publish_id. The actual public video ID arrives later via TikTok's post.publish.publicly_available webhook. Until that webhook fires (typically seconds, occasionally up to a minute under load), analytics for the post will return an empty metrics object. If you're polling immediately after a Publish Now call, expect the first sweep or two to be empty for TikTok.

TikTok: video.list scope (added May 2026)

TikTok analytics requires the video.list scope, which Quegly added on 2026-05-10. Users who connected their TikTok account before that date have older tokens missing this scope. Their analytics calls return "Video not found (deleted, private, or scope not granted)." Fix: have the user disconnect and reconnect their TikTok account in the dashboard. The new token grants the scope and analytics start working immediately.

X: url_link_clicks / user_profile_clicks only within 30 days

X gates the non_public_metrics field set (link clicks, profile clicks) to a 30-day window after publish. For tweets older than 30 days, those two keys disappear from the response. The rest of the metrics keep working. We age-check posted_at before requesting the field set, so older tweets won't error; they just return without the click attribution data.

YouTube: watch-time / retention / subscriber metrics require a re-connect

The YouTube fetcher pulls basic stats (views, likes, comments, favorites) from the Data API. Always granted. Watch-time, retention percentage, and subscriber attribution come from the YouTube Analytics API, which needs the yt-analytics.readonly scope. Quegly added that scope on 2026-05-10; older tokens fall back to Data-API-only metrics. The fetcher fails open (the call succeeds, just without the extra fields), so this surfaces as "the YouTube entry is missing watch_time_seconds and the rest" rather than as an error. Fix: same as TikTok. Disconnect and reconnect to grant the new scope.

LinkedIn: stats need the member's permission

LinkedIn post stats need the r_member_postAnalytics permission, which the member grants when connecting LinkedIn. A member who connected before Quegly asked for it has not granted it: their LinkedIn entries keep metrics as {} and are not refreshed at all. Fix: reconnect LinkedIn on the dashboard's Manage page and allow it. LinkedIn also limits stats calls per member and per app each day; past the limit, a sweep reports LinkedIn posts as failed until midnight UTC, and they keep their last numbers.

Defensive coding tip: when reading the metrics object, always treat keys as optional. A missing key means "the platform didn't expose it for this post" (not in the user's scope, outside the analytics window, or platform-side null). Use metrics.likes ?? 0 rather than assuming the key exists.