Analytics API
Views, likes, comments and shares for every account you have connected, in one shape whatever the platform underneath.
Base URL
https://api.multi-upload-tool.com/api/v1
Authentication
All requests require an API token:
x-api-key: YOUR_API_TOKENAnalytics are gated on the plan’s analytics feature. A plan without it gets 403 on every endpoint on this page.
Where the numbers come from
They are not read from the platform when you ask. A background collector refreshes every eligible account once a night and writes a snapshot; these endpoints serve that store.
That is deliberate, and it is what makes one request answer for every account at once instead of one call per account per platform, each with a different shape. It also buys history: no platform hands back the follower count you had last Tuesday, so a figure nobody measured that day is gone for good.
The consequence is that every number has an age. Read lastRefreshedAt and show it. A number without its reading time looks live, and the first time it disagrees with the platform’s own app — which it will, because the two were read at different moments — it reads as a bug rather than a collection that has not run yet.
| Pass | When |
|---|---|
| Incremental (≈40–50 most recent posts per account) | every night, 03:00 UTC |
| Full crawl (up to 1000 posts per account) | Sundays |
| On demand, one account | POST /analytics/:id/sync |
What each platform reports
A metric a platform does not expose is returned as 0, not omitted. Read this table before computing an average across platforms.
| Platform | Views | Likes | Comments | Shares |
|---|---|---|---|---|
| TikTok | yes | yes | yes | yes |
| yes | yes | yes | yes | |
| YouTube | yes | yes | yes | not reported |
| yes (Page needs ≥100 likes) | yes | yes | yes | |
| Threads | yes | yes | yes | yes |
| impressions | yes | yes | organisation Pages only | |
| impressions | not reported | not reported | saves | |
| Bluesky | not reported | yes | replies | reposts + quotes |
| X | impressions | yes | replies | reposts + quotes |
X bills every read, so X accounts are synced once a day and only the posts published through Multi Upload Tool in the last 29 days are read, plus the follower count. Posts published directly on X are not tracked.
LinkedIn personal profiles expose no post analytics — only organisation Pages do. A personal account keeps publishing fine and returns empty charts, not an error.
Global analytics
Aggregates every active account the token can see.
Endpoint
GET /analytics/overview
Query parameters
| Parameter | Type | Description |
|---|---|---|
tags | string | Comma-separated account tags. Only accounts carrying one of them are aggregated. |
timezone | string | IANA zone the postingInsights buckets are read in, e.g. Europe/Paris. Defaults to UTC. |
source | string | mut keeps only posts published through Multi Upload Tool in videos, external only posts made directly on the platform. all (default) keeps both. Totals and summary always cover every post. |
Example Request
curl "https://api.multi-upload-tool.com/api/v1/analytics/overview?tags=client-a,client-b" \
-H "x-api-key: YOUR_API_TOKEN"Example Response
{
"success": true,
"data": {
"overview": {
"totalViews": 1284300,
"totalLikes": 41200,
"totalComments": 2870,
"totalShares": 1930,
"videoCount": 194,
"accountCount": 6,
"bestAccount": { "name": "@your_account", "platform": "tiktok", "views": 662700 }
},
"videos": [],
"bestPerforming": { "mostViewed": {}, "mostEngaging": {} },
"topHashtags": [],
"dailyHistory": [],
"platformHistory": [],
"summary": {
"windowDays": 30,
"views": { "current": 184200, "previous": 151000, "change": 33200, "changePct": 22, "measuredDays": { "current": 30, "previous": 28 } },
"followers": { "total": 48210, "previous": 46900, "change": 1310, "changePct": 2.8 },
"postsPublished": { "current": 42, "previous": 37, "change": 5 }
},
"followerHistory": [{ "date": "2026-09-20", "followers": 48105 }, { "date": "2026-09-21", "followers": 48210 }],
"lastRefreshedAt": "2026-09-21T03:04:11.000Z",
"neverSynced": false,
"syncErrors": []
}
}neverSynced: true means nothing has ever been collected, as opposed to collected and empty. The two deserve different messages: “connect the account and wait for tonight” is not “you have no posts yet”.
syncErrors lists the accounts whose last collection failed, one entry each. A failing account never stops the others.
Period comparison and followers
summary compares the last 30 days with the 30 days before:
views: views gained in each period, not lifetime totals. Summed from the same daily series as the chart, so a missed night is spread evenly rather than counted twice.measuredDayssays how many days of each period had data.followers:totalis the latest follower count summed over the accounts in scope.changeonly counts accounts that were also measured 30 days ago — an account connected last week adds tototal, not tochange.postsPublished: posts published in each period, whichever tool posted them.
null means not measured, never zero. Follower counts are collected from 2026-09-24 onwards, so followers.change stays null until an account has 30 days of history.
followerHistory gives one point per day: the sum of each account’s last known follower count. Every post in videos also carries source — mut if it was published through Multi Upload Tool, external if it was posted directly on the platform.
| Platform | Follower count |
|---|---|
| TikTok, Instagram, Facebook, Threads, Pinterest, Bluesky, X | followers |
| YouTube | subscribers (omitted when the channel hides it) |
| organisation Page followers; personal profiles are not collected |
Best time to post
Every response also carries postingInsights: the weekdays and hours this account has published in, averaged by views.
{
"postingInsights": {
"timezone": "Europe/Paris",
"minSamples": 3,
"sampleSize": 47,
"byWeekday": [
{ "weekday": 0, "posts": 2, "avgViews": 18400, "ranked": false },
{ "weekday": 1, "posts": 9, "avgViews": 12100, "ranked": true }
],
"byHour": [{ "hour": 0, "posts": 0, "avgViews": 0, "ranked": false }],
"bestDay": { "weekday": 4, "posts": 11, "avgViews": 21300, "ranked": true },
"bestHours": [{ "hour": 18, "posts": 7, "avgViews": 24800, "ranked": true }]
}
}byWeekday always has 7 entries (0 = Sunday, following Postgres’ dow) and byHour always has 24, including the empty ones, so a chart never has to fill in gaps.
Always pass a timezone. The default is UTC, and an hour-of-day ranking read in the
wrong zone is off by the audience’s whole offset — silently, because every bucket still
holds a plausible number.
A bucket with fewer than minSamples posts comes back with ranked: false. It keeps its average — the number is real and worth showing — but it is never eligible to be bestDay or to enter bestHours. One lucky post is a coincidence, not a slot, and it is the loudest one: a single viral video gives its bucket an average no honest bucket can beat. When nothing has reached the floor yet, bestDay is null and bestHours is empty; say so rather than crowning a winner.
Analytics for one account
Same shape, restricted to a single account, plus that account’s tags.
Endpoint
GET /analytics/:connectedAccountId
Example Request
curl https://api.multi-upload-tool.com/api/v1/analytics/cmf3k9x2a0001l408q7vbn2hd \
-H "x-api-key: YOUR_API_TOKEN"Refresh an account now
Reads the platform inside the request, writes the snapshots, then returns the refreshed analytics. Covers up to the 1000 most recent posts. Use it when someone has just published from the platform’s own app and wants to see it immediately.
Endpoint
POST /analytics/:connectedAccountId/sync
Example Request
curl -X POST https://api.multi-upload-tool.com/api/v1/analytics/cmf3k9x2a0001l408q7vbn2hd/sync \
-H "x-api-key: YOUR_API_TOKEN"This is a write: it rotates OAuth tokens, stores snapshots and consumes the platform’s rate limit.
It requires manage access to the account, not read access, and a team MEMBER with view-only access gets 403.
LinkedIn post comments
Lists who commented on a single LinkedIn post. LinkedIn only — any other platform returns 400. Read-only: there is no endpoint to reply.
Endpoint
GET /analytics/:connectedAccountId/linkedin/comments?postId=...
Example Request
curl "https://api.multi-upload-tool.com/api/v1/analytics/cmf3k9x2a0001l408q7vbn2hd/linkedin/comments?postId=urn:li:share:7212345678901234567" \
-H "x-api-key: YOUR_API_TOKEN"Example Response
{
"success": true,
"data": {
"comments": [
{ "author": "Jane Doe", "text": "Great post!", "createdAt": "2026-09-18T09:12:00.000Z" }
]
}
}Errors
| Status | Meaning |
|---|---|
401 | Missing or invalid x-api-key. |
403 | The plan does not include analytics, or the account belongs to someone else. |
404 | No such connected account. |
429 | Rate limited — 100 requests per 15 minutes. |