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.
/v1/analytics/post/{id}Get post analyticsPath Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | The post ID |
Example Request
curl https://api.quegly.com/v1/analytics/post/post-abc123 \
-H "Authorization: qgl_your_api_key"Response
{
"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.
/v1/analyticsBulk workspace analyticsQuery Parameters
| Parameter | Type | Description |
|---|---|---|
start_date | string | ISO date. Defaults to 30 days ago. |
end_date | string | ISO date. Defaults to now. |
Example Request
curl "https://api.quegly.com/v1/analytics?start_date=2026-06-01&end_date=2026-06-30" \
-H "Authorization: qgl_your_api_key"Response
{
"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.
/v1/analytics/refreshOn-demand analytics refreshExample Request
curl -X POST https://api.quegly.com/v1/analytics/refresh \
-H "Authorization: qgl_your_api_key"Response
{
"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.
From the post-March-2025 metric set on the Graph API media insights endpoint.views replaced the deprecated impressions.
| Parameter | Type | Description |
|---|---|---|
views | number | Total views (Reels) or impressions (Feed/Carousel) post-March 2025. |
reach | number | Unique accounts that saw the post. |
likes | number | Like count. |
comments | number | Comment count. |
saved | number | Times the post was saved. |
shares | number | Times the post was shared (DM, story re-share). |
total_interactions | number | Aggregate of likes + comments + saves + shares (Meta-computed). |
watch_time_seconds | number | Reels only. Total seconds watched across all viewers. |
avg_view_duration_seconds | number | Reels only. Average seconds watched per view. |
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.
| Parameter | Type | Description |
|---|---|---|
views | number | post_views for Page Posts; lifetime view count for Video nodes. |
likes | number | Reaction count (mirrors `reactions` for cross-platform parity). |
reactions | number | Page Posts only. Total reactions count summary. |
comments | number | Comment count. |
shares | number | Page Posts only. Share count. |
post_clicks | number | Page 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).
| Parameter | Type | Description |
|---|---|---|
views | number | Lifetime view count. |
likes | number | Like count. |
comments | number | Comment count. |
favorites | number | Legacy field, mostly 0 in modern data. |
shares | number | Analytics API only. Share count for the lookback window. |
watch_time_seconds | number | Analytics API only. Total seconds watched (estimatedMinutesWatched × 60). |
avg_view_duration_seconds | number | Analytics API only. Average seconds watched per view. |
avg_view_percentage | number | Analytics API only. Percentage of the video watched on average (0 to 100). |
subscribers_gained | number | Analytics API only. New subscribers attributable to this video. |
subscribers_lost | number | Analytics 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).
| Parameter | Type | Description |
|---|---|---|
views | number | Video view count. |
likes | number | Like count. |
comments | number | Comment count. |
shares | number | Share 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.
| Parameter | Type | Description |
|---|---|---|
views | number | Impression count (mirrors `impressions` for cross-platform parity). |
impressions | number | Same value as `views`, kept distinct for clarity. |
likes | number | Like count. |
comments | number | Reply count (mapped to `comments` for cross-platform parity). |
retweets | number | Retweet count. |
quotes | number | Quote count. |
bookmarks | number | Bookmark count. |
url_link_clicks | number | non_public_metrics. Clicks on links inside the tweet. 30-day window only. |
user_profile_clicks | number | non_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.
| Parameter | Type | Description |
|---|---|---|
views | number | View count. |
likes | number | Like count. |
comments | number | Reply count (mapped to `comments` for cross-platform parity). |
reposts | number | Repost count. |
quotes | number | Quote count. |
shares | number | Share count. |
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.
| Parameter | Type | Description |
|---|---|---|
views | number | Impressions. |
reach | number | Unique members reached. |
likes | number | Reaction count (mirrors `reactions` for cross-platform parity). |
reactions | number | Total reactions, every kind. |
comments | number | Comment count. |
reposts | number | Repost count. |
shares | number | Times the post was sent in LinkedIn messages. |
saved | number | Times the post was saved. |
url_link_clicks | number | Clicks on links in the post. |
watch_time_seconds | number | Video 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.
| Parameter | Type | Description |
|---|---|---|
likes | number | Like count. |
comments | number | Reply count (mapped to `comments` for cross-platform parity). |
reposts | number | Repost count. |
quotes | number | Quote count. |
bookmarks | number | Bookmark count. |
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).
| Parameter | Type | Description |
|---|---|---|
views | number | Impressions. |
likes | number | Reactions (mapped to `likes` for cross-platform parity). |
comments | number | Comment count. |
saved | number | Times the Pin was saved. |
post_clicks | number | Clicks on the Pin. |
url_link_clicks | number | Clicks 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.
| Property | Value |
|---|---|
| Cache TTL | Age-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 window | 30 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 signal | The `fetched_at` / `captured_at` field on each platform entry tells you when that snapshot was last refreshed. |
| Concurrency | Up 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.