Instagram, Facebook & Threads Post Metrics Tracker
Pricing
from $3.00 / 1,000 post trackeds
Instagram, Facebook & Threads Post Metrics Tracker
Track Instagram, Facebook and Threads post metrics over time. Put your post URLs on a schedule and every run returns likes, comments, shares, views and followers, plus the change since the last run and since the first. Export to CSV, Excel or JSON, or use the API.
Pricing
from $3.00 / 1,000 post trackeds
Rating
0.0
(0)
Developer
CHEN-SHU-CHEN
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
Track Instagram, Facebook and Threads post metrics over time. Give it your campaign's post URLs once and put it on a Schedule: every run returns each post's likes, comments, shares, views and followers, next to the change since the last run and since the first.
A single export tells you how a post is doing today. This tells you how much it has grown since launch and since yesterday, without keeping old exports and subtracting them by hand. Instagram, Facebook and Threads can be mixed in one list, and every row has the same columns.
It tracks posts you already know about. If you need to find posts by hashtag, keyword or account, use a platform-specific scraper; this one takes URLs and nothing else.
How to track Instagram, Facebook and Threads post metrics
- Paste your post URLs into Post URLs. Instagram posts and reels, Threads posts and Facebook posts, reels and videos can be mixed.
- Enter a Tracker name, one per campaign, such as
spring-launch. It names the history this run reads and adds to. - Click Start. The first run is the baseline: every row has
snapshotCount: 1and null change columns. - Open Schedules in Apify Console and create a schedule. Daily is enough for most campaigns; hourly is useful in the first day or two after a post goes up.
- Add this Actor to the schedule with the same input, above all the same Tracker name.
- Read the results in the run's dataset, export them to CSV, Excel or JSON, or fetch them through the Apify API. To change the list later, edit the input saved on the schedule; existing posts carry on and new ones start their own history.
How the tracking history works
Each run files a snapshot of every post it reads in a key-value store named after your tracker, post-tracker-{trackerName}. Named storages on Apify are kept until you delete them, so the next run, a day or a month later, finds the history there and measures against it.
Use a different Tracker name per campaign. Two campaigns under one name share one history, and a post in both would have its growth counted from whichever run saw it first.
- Adding a URL later is fine. It starts its own history on the run that first sees it, and its change columns are null until its second reading.
- Removing a URL keeps its history. Add it back and the change is measured from its last reading, however long ago.
RUN_SUMMARYlists tracked posts that were left out of the run, so a URL dropped by mistake does not go unnoticed. - The same post twice in one list, say once with a share link's
?igsh=and once without, is read and charged once, and gets a row for each line.
Unknown is null, never 0, and so is the change
A post whose author hid its like count reports likeCount: null, and the change involving it is null too. A post that showed 500 likes today after hiding them yesterday did not gain 500 likes. Each metric stands on its own, so a hidden like count does not blank the change in views.
A count that goes down is reported as a negative change. People withdraw likes and platforms remove spam comments; that is real movement, not an error.
Deleted posts keep their last-known numbers
When a post disappears, its row comes back with status: "not_found" and the last values ever read for it, flagged with valuesAreLastKnown: true, lastKnownAt and notFoundSince. The change columns are null, because nothing was read to compare. The same applies to any row that could not be read this run: a login wall, a parse failure, or a URL left unprocessed when the run stopped early. To sum only fresh readings, filter on valuesAreLastKnown.
Every input URL still yields a row. 100 URLs in, 100 rows out.
How much does it cost to track post metrics?
Pricing is pay per event. You pay for each post read on each run, and nothing for the platform usage on top:
| Event | Charged for | Price |
|---|---|---|
post-tracked | Each Instagram or Threads post read, per run | $0.003 |
post-tracked-facebook | Each Facebook post read, per run | $0.006 |
post-unavailable | Each post that could not be read, per run | $0.0005 |
Facebook has its own price because it is the one platform that needs residential proxies, and its video pages are five times the size of a photo page. A flat rate would have Instagram and Threads subsidising it.
What is not charged?
Duplicates within one list, URLs left unprocessed when a run stops early, and unsupported URLs. If the platform restarts a run partway through, the posts it already finished are not read again, and their rows are not repeated.
A Facebook post that failed to load on 3 runs in a row is tried again once a day, not on every run; on the runs in between it comes back not_processed with its last-known values, uncharged. Within one run, after 5 Facebook posts in a row fail to load, the rest of that run's Facebook posts are skipped the same way, uncharged, whatever abortAfterConsecutiveFailures says; Instagram and Threads carry on.
A run also stops, leaving the rest of its posts not_processed and uncharged, if it is plainly costing more than it earns: for example, a proxy of your own that stalls every request, or a long list of Facebook posts that all turn out deleted. A list takes up to 5,000 URLs.
How much does a daily schedule cost?
A daily schedule over 50 posts is 50 events a day. A deleted post is reported, and charged as post-unavailable, on every run until you remove it from the list; it is fetched again only once every 7 days, in case it comes back, and on the runs in between its row comes from the tracker's record. If you set a maximum charge per run, the run checks it before reading each post and stops when the next post could not be paid for: posts read so far keep their snapshots, and the rest come back as not_processed rows with their last-known values, uncharged. If charging itself keeps failing, the run stops the same way rather than carry on unbilled.
Input example
{"postUrls": ["https://www.instagram.com/p/DeclSZmF_7I/","https://www.threads.com/@nasa/post/Decc755lYhC"],"trackerName": "my-campaign","fetchFollowers": true,"writeHistoryDataset": false}
| Field | Description |
|---|---|
postUrls | Post URLs. Platforms can be mixed freely. |
trackerName | Which history to read and add to. One per campaign, the same on every run. Letters, digits and hyphens, up to 40 characters. |
writeHistoryDataset | Also append every reading to the dataset post-tracker-{trackerName}-history, one row per post per run. Off by default. |
maxSnapshotsPerPost | How many readings of each post to keep. Default 1000. |
fetchFollowers | Look up each author's follower count, so follower growth is tracked too. Costs one extra request per account. |
abortAfterConsecutiveFailures | Stops the run when a platform stops responding. Deleted posts do not count toward this. |
proxyConfiguration | For Instagram and Threads. Datacenter proxies are enough. Residential and Google SERP groups are not used here, since the price per post is set for datacenter traffic: a run that asks for them falls back to the datacenter proxy and says so in RUN_SUMMARY. Your own proxy URLs are used as given. |
facebookProxyConfiguration | For Facebook only, which needs residential proxies. |
Accepted URL shapes: Instagram /p/ and /reel/; Threads on threads.com or threads.net; Facebook {pageId}_{postId}, permalink.php, /posts/pfbid…, /reel/, /videos/, /photo/?fbid=.
Output example
One row per input URL. This is a post on its second run:
{"status": "success","platform": "Instagram","postUrl": "https://www.instagram.com/p/DeclSZmF_7I/","username": "nasa","mediaType": "carousel","likeCount": 1918845,"likeCountDelta": 33,"likeCountSinceFirst": 33,"replyCount": 6013,"replyCountDelta": 0,"viewCount": null,"viewCountDelta": null,"observedAt": "2026-10-18T09:12:40.000Z","previousAt": "2026-10-18T09:12:04.000Z","hoursSincePrevious": 0.01,"snapshotCount": 2,"valuesAreLastKnown": false}
viewCount is null because image and carousel posts have no plays, and so its change is null too.
| Field | Notes |
|---|---|
likeCount, replyCount, shareCount, viewCount, followerCount | Current likes, comments, shares, views and followers. On a row that could not be read, the last-known values. |
likeCountDelta … followerCountDelta | Change since the post's previous reading. Null on its first run, and whenever either reading is unknown. |
likeCountSinceFirst … followerCountSinceFirst | Change since the post's first reading. Same null rules. |
observedAt | When this run read the post. Null if it could not. |
previousAt, hoursSincePrevious | When the previous reading was, and how long ago. A run that could not read the post leaves a longer gap than the schedule's interval. |
firstSeenAt, hoursSinceFirst | When the first reading was, and how long ago. |
snapshotCount | Readings of this post so far, this one included. |
valuesAreLastKnown, lastKnownAt | true when the counts are from an earlier run, and when that was. |
notFoundSince | When a deleted post was first found missing. |
The post's own fields are the same as in Instagram, Facebook, Threads & YouTube Post Metrics: status, platform, postUrl, username, authorUrl, mediaType, createdAt, text, hashtags, mentions. repostCount and quoteCount are kept for Threads but have no change columns of their own; shareCount, their sum, does.
The run's key-value store also holds FAILED_URLS, with the reason each failed URL could not be read, and RUN_SUMMARY, with counts, the names of the history store and dataset, and any tracked posts missing from this run's list.
History dataset for charts
With writeHistoryDataset on, every successful reading is appended to post-tracker-{trackerName}-history as trackerName, postUrl, platform, username, observedAt and the seven counts. A line chart of a post over time is a filter on postUrl with observedAt on the x-axis. Rows for posts that could not be read are not added: a gap in the line is honest, a flat stretch of copied values is not.
It starts from the run you turn it on, and it only grows. Delete it from Storage when the campaign is over.
Other Actors for social media post metrics
| Actor | Use it when |
|---|---|
| Instagram, Facebook, Threads & YouTube Post Metrics | You need the numbers once, for a report, without keeping a history. |
| Instagram Engagement Rate Calculator & Audit | You are choosing creators and want each account's engagement rate, posting frequency and standout posts. |
FAQ
What happens if two runs of one tracker start at once?
Both run, and both are charged for the posts they read: each is a reading you asked for. No reading is lost. Each run also keeps its own readings in a record of its own, which no other run writes, and adds to it after every post. If the two write a post's history at the same moment and one overwrites the other's reading, the next run puts that reading back, however long the schedule's gap is. A run's record is deleted only after two later checks, at least 72 hours apart, both find every reading in it filed in the history. Checks happen when a run starts, so a record lives for roughly 3 days plus up to two schedule intervals: about 4 days on a daily schedule, up to about 2–3 weeks on a weekly one. Each record holds only that run's readings, so the storage this takes stays small. Until the next run, a reading lost that way is missing only from the stored history, not from either run's output.
What if the platform restarts a run?
Apify sometimes moves a running Actor to another server. The tracker saves how far it got after every post, and starts no new post once the move is announced, so the restarted run carries on from where it stopped instead of from the top. Before going on, it checks the run's dataset: a row that went out just before the restart is not pushed again and its post is not read again, only its charge is completed. If that dataset cannot be read after three tries, the run stops and fails instead of guessing, so no post is reported or charged twice. Two narrow gaps remain. If the run died after charging a post but before saving that it had, the charge is sent again with the same key, which Apify ignores only within three minutes of the first. And if the history dataset is on, the one reading in flight at that moment can be missing from it; the stored history and the output row still have it.
What if a link carries a login token?
Links copied from an app or an admin tool sometimes carry an access_token, an api_key, a session id, a signature or a user:password@ before the host. The post is read with the link as given, but that part of it is removed from everything the tracker keeps or shows: the rows, the log, the stored history and its index, and FAILED_URLS. Error messages that quote the link are cleaned the same way.
Is the change measured per day?
No, per reading. If a run fails to read a post, its next reading's change covers both intervals. hoursSincePrevious tells you how long that was.
Are share links and plain links the same post?
URLs that differ only in share parameters (igsh, utm_*, fbclid and the like), letter case in the host, www. or a trailing slash count as the same post. Two genuinely different forms of one Facebook post, a /posts/pfbid… link and the permalink.php link, are not recognised as the same and keep separate histories. Use one form per post.
How much history is kept?
Up to maxSnapshotsPerPost readings per post, 1000 by default, which is almost three years of daily runs. The first reading is always kept, so the change since the first stays exact; beyond the cap the oldest of the rest are dropped. snapshotCount keeps counting every reading.
Can it track reach, impressions or private accounts?
No. Reach and impressions are visible only to a page's own administrators, and no public scraper can produce them. Private accounts, comment text and posts behind a login are out of reach too.
Why does an Instagram follower count barely move?
On Instagram single-image posts the follower count comes from displayed text, rounded above ~10K, so small follower changes on large accounts do not show up there. Threads follower counts are exact. Facebook's are exact when the page's data carries the number, and otherwise fall back to the displayed text, which is rounded too ("2.8M followers").
A follower change between two readings can therefore partly reflect a switch between a rounded and an exact reading rather than real growth: from 2,800,000 (rounded) to 2,812,345 (exact) is not 12,345 new followers. Treat followerCountDelta on large Instagram and Facebook accounts as approximate.
Why is memory limited to 1 GB?
The Actor reads pages over plain HTTP and peaks at about 110 MB, so 1 GB is plenty. More memory would not make it faster, only more expensive, so runs are held between 512 MB and 1 GB.
What if Meta changes its pages?
Extraction depends on undocumented internal JSON, which Meta can change without notice. Failures are reported per URL, with the last-known values, rather than crashing the run. If you see a run of parse_failed rows, open an issue on this Actor.