Instagram Bot / Fake-Follower Heuristic Analyzer
Pricing
$1.60 / 1,000 profile analysis results
Instagram Bot / Fake-Follower Heuristic Analyzer
Analyze public Instagram profiles for suspicious audience-quality signals using transparent heuristics. Export flat CSV-friendly bot-risk scores, confidence fields, and reason tags for influencer vetting, creator screening, and manual review.
Pricing
$1.60 / 1,000 profile analysis results
Rating
0.0
(0)
Developer
Delowar Munna
Maintained by CommunityActor stats
1
Bookmarked
201
Total users
8
Monthly active users
16 days ago
Last modified
Categories
Share
Instagram Fake Follower Checker — Transparent Bot-Risk Analyzer

Screen public Instagram profiles for fake-follower and bot-like risk in seconds — and get an answer you can actually defend. Every score comes with the exact thresholds that produced it, a separate confidence rating so you know how much evidence was behind it, a plain-English reason you can paste into a client email, and a version stamp so you can tell when our scoring changed and yours didn't. One flat, spreadsheet-ready row per profile. No Instagram login, no cookies, no proxy setup, no private data — ever.
Positioning: This is an authenticity audit aid, not a definitive fraud detector. Anyone claiming to prove fake followers from public data is overselling. We show you our work instead. Every score is heuristic and should be combined with manual review before any partnership decision.
Quick start
- Paste your creator handles into Instagram Usernames — with or without
@. Full profile URLs work in the same field, so a column copied straight out of a spreadsheet is fine. - Press Start.
- Export the Profile Audit Rows dataset to CSV, Excel, or Google Sheets.
That's it. There is nothing to configure, no proxy to pick, and no account to connect. The defaults are tuned for influencer vetting.
What one result represents
One billable result = one audit row for one processed public profile.
Rows we cannot analyze are never billed. If a profile is private, deleted, renamed, or unreachable, the actor still pushes a row — so you can see exactly which handles failed and why — but that row carries botRiskBucket: "unknown" and no charge is made for it. You pay for analysis, not for our failures. Profiles skipped by the maxProfiles cap are never fetched and never billed.
What does this actor do?
For each public Instagram username (or profile URL) you provide, the actor:
- Fetches the profile's public metrics (followers, following, posts, bio, category, verification, business signals).
- Optionally samples a bounded set of recent visible posts/reels and reads their like / comment / view counts.
- Computes derived ratios (engagement rate approximation, comments-to-likes, followers-to-following, etc.).
- Evaluates a set of named, auditable heuristic flags (low engagement for tier, sparse content, high following, low recency, inconsistent performance, suspicious growth proxy, profile completeness, business credibility offset).
- Produces a conservative bot-risk score (0–100), a bucket (
low/moderate/elevated/high/unknown), a confidence score, a manual review priority, and a short reason field — one row per analyzed account.
The output is intentionally flat, CSV-friendly, and designed for analyst review — not for unilateral decisions.
Use cases
- Influencer vetting — pre-screen creators before gifting / sponsorship deals.
- Agency pre-screening — quickly flag profiles needing manual review across a creator shortlist.
- Competitor authenticity benchmarking — compare suspicious-signal patterns across competitor accounts.
- Marketplace and CRM triage — produce a CSV of candidate accounts with risk-style fields.
- Research — structured public-signal exports for authenticity-related studies.
What it is — and isn't
It is a public-signal analyzer, a risk-scoring helper, an authenticity audit support tool.
It is not a verified fraud detector, a replacement for manual review, an official Instagram trust signal, a private-data analyzer, or a tool for follower-by-follower forensics.
Sample inputs
Example 1 — Influencer vetting batch (defaults)
Screen a shortlist of public creator/brand accounts using the default analysis settings.
{"usernames": ["instagram", "therock", "nike"]}
Example 2 — Mixed handles, @-prefixed names, and URLs in one field
Everything below resolves to the same kind of row. Duplicates are removed automatically, so @natgeo and https://www.instagram.com/NATGEO/ collapse into a single analyzed profile.
{"usernames": ["google","@nasa","https://www.instagram.com/tesla/","https://www.instagram.com/natgeo/"],"maxProfiles": 50,"recentPostsLimit": 6}
Input reference
| Field | Type | Default | Description |
|---|---|---|---|
usernames | string[] | ["natgeo"] | Public Instagram profiles. Accepts bare handles, @handles, and full profile URLs, mixed freely. Post / reel URLs are rejected. |
maxProfiles | integer | 100 | Safety cap on unique profiles analyzed per run (1–1000), applied after deduplication. Profiles beyond the cap are never fetched and never billed. |
analysisDepth | string | standard | quick / standard / deep / custom — see below. Collapses the sampling decision into one choice. |
recentPostsLimit | integer | 9 | Max recent posts analyzed per profile (0–24). Only used when analysisDepth is custom; set it to 0 to analyze without a post sample. |
minPostsForFullConfidence | integer | 3 | Threshold used by the confidence score. Not a hard requirement — profiles with fewer visible posts still produce a row, just with lower confidence. |
requestTimeoutSecs | integer | 180 | Internal, hidden. Timeout per profile. Tuned for you. |
maxConcurrency | integer | 1 | Internal, hidden. Held at 1 to reduce rate-limit pressure. |
debugMode | boolean | false | Internal, hidden. Verbose logging. |
minVisiblePostsRequired | integer | — | Legacy, hidden. Alias for minPostsForFullConfidence. If both are set, the new name wins. |
profileUrls | string[] | [] | Legacy, hidden. Put profile URLs in usernames instead. Entries here are merged and deduped with usernames. |
includeDerivedSignals | boolean | true | Legacy, hidden — always on, value ignored. See below. |
includeProfileContext | boolean | true | Legacy, hidden — always on, value ignored. |
includeRecentPostsSample | boolean | true | Legacy, hidden — always on, value ignored. Use analysisDepth: quick, or custom with recentPostsLimit: 0, to analyze without a post sample. |
Fields marked hidden do not appear in the Console form. The internal ones are tuned for you and need no attention; the legacy ones are superseded or always-on. All of them remain accepted over the API, so existing saved tasks and API integrations that still set them keep running — the three include* switches are simply ignored now rather than rejected.
Why the include* switches are gone
They only ever removed columns from a fixed 71-column schema, and they saved nothing: recent posts arrive embedded in the profile response, so sampling them costs no extra request.
includeDerivedSignals was worse than useless — it was unsafe. The bot-risk score is built on engagementRateApprox, which was only computed when that switch was on. Turning it off silently disabled the four heaviest risk flags while confidence still reported 100. On a blatantly bot-like profile the row flipped from 68 / elevated / isLikelyBot: true to 10 / low / isLikelyBot: false, with the reason reading "No suspicious signals detected on visible public data" — a confident all-clear based on evidence the Actor had already collected and then discarded. Nobody unchecking "compute ratio fields" expects a risk verdict to invert, so the switch was removed rather than repaired. Your scores no longer depend on a checkbox.
Handles and profile URLs go in the same usernames field and can be mixed freely — duplicates are deduped automatically, case-insensitively, so @natgeo and https://www.instagram.com/NATGEO/ collapse into one analyzed profile.
Analysis depth
| Depth | Posts sampled | Use when |
|---|---|---|
quick | 0 | You only need profile-level ratios. Fastest and cheapest, but engagement flags cannot fire and confidence caps around 60. |
standard | 9 | Default. Right for influencer vetting. One API call per profile. |
deep | ~12 (everything Instagram exposes) | You want the strongest median / variance / posting-cadence evidence. |
custom | Whatever you set | You want to drive includeRecentPostsSample and recentPostsLimit yourself. |
Why
deepis 12 and not 24: Instagram embeds roughly 12 recent posts in its profile response, and its feed endpoint returns no more than that either without cursor pagination — we measured it. Asking for 24 spends an extra request and returns the same 12, so we don't pretend otherwise.
Row status
Every row carries a status, so a run's failures are filterable rather than guessable:
status | Meaning | Billed? |
|---|---|---|
ok | Fully analyzed | ✅ |
partial | Analyzed, but some evidence was unavailable — e.g. recovered from page metadata only, so no engagement signals | ✅ |
private | Account is private; no authenticity conclusion possible | ❌ |
not_found | Profile does not exist, was deleted, or was renamed | ❌ |
unavailable | Instagram required a login or rate-limited the request | ❌ |
error | Upstream/network failure | ❌ |
errorCode and errorMessage carry the machine-readable and human-readable detail. partial rows bill because they carry real follower metrics and a real scored result — see What one result represents.
Output
One JSON object per analyzed profile is pushed to the default dataset. The dataset has a single view named Profile Audit Rows.
Profile Audit Rows — table view

Row groups
- Identity:
inputUsername,profileUsername,profileUrl,instagramUserId,fullName,isVerified,isPrivate,accountType,categoryName,profilePicUrl,hasDefaultProfilePic - Public metrics:
followersCount,followingCount,postsCount,followersToFollowingRatio,followersBucket,biography,externalUrl,hasExternalUrl - Recent content sample:
recentPostsAnalyzed,avgLikesRecent,avgCommentsRecent,avgViewsRecent,medianLikesRecent,medianCommentsRecent,medianViewsRecent,maxLikesRecent,maxCommentsRecent,latestPostTimestamp,daysSinceLatestPost,recentActivitySignal - Derived ratios:
engagementRateApprox,commentsToFollowersRatio,likesToFollowersRatio,viewsToFollowersRatio,commentsToLikesRatio,engagementVarianceHint - Heuristic flags:
lowEngagementFlag,highFollowerLowEngagementFlag,highFollowingFlag,sparseContentFlag,lowRecencyFlag,inconsistentPerformanceFlag,suspiciousGrowthProxyFlag,profileCompletenessFlag,defaultProfilePicFlag,usernamePatternFlag,postingRegularityFlag,businessCredibilitySignal - Scores:
botRiskScore,botRiskBucket,confidenceScore,confidenceBucket,signalCount,riskSignalCount,credibilitySignalCount,evidenceCoverageScore,scoreVersion - Explanation:
botRiskReason,topFlags(pipe-delimited, strongest first),riskSignals,credibilitySignals,dataQualityWarnings,isLikelyBot,manualReviewPriority - Utility:
scrapedAt,runId,sourceActorVersion
isLikelyBot — a filter, not a verdict
isLikelyBot exists so you can sort a shortlist in one click. It is not a separate judgement and it adds no new information — it is a saved filter over two columns already in the row, with this exact definition:
isLikelyBot = (botRiskBucket is "elevated" or "high") AND (confidenceBucket is "high")null when botRiskBucket is "unknown"
Three things follow from that definition, and they're the point:
- It requires high confidence. A high risk score we aren't confident about is exactly the case a bare boolean would misrepresent — so it stays
falserather than accusing on thin evidence. - It returns
null, notfalse, when we have no data. "We couldn't see anything" is not "not a bot." - It can never disagree with the score, because it's derived from it. If you disagree with
isLikelyBot, readbotRiskReason— the reasoning is right there.
If you want a defensible answer, use botRiskScore + confidenceScore + botRiskReason. isLikelyBot is a convenience, and we'd rather tell you that than pretend it's a classifier.
Evidence and data quality
evidenceCoverageScore(0–100) — how much of the evidence we wanted was actually available. Distinct fromconfidenceScore, which also weighs privacy and profile completeness.dataQualityWarnings— pipe-delimited, CSV-safe. e.g.no_recent_posts_sampled,private_profile,profile_picture_undetermined,too_few_posts_for_cadence,thin_views_sample. Every warning here describes something Instagram withheld — never something we chose not to compute.- On view metrics: only video/reel posts have view counts, so
avgViewsRecent/medianViewsRecent/viewsToFollowersRatiodescribe the video posts within the sample, not all of it. When fewer than 3 videos back those figures,thin_views_samplesays so. Photo posts are excluded rather than counted as zero views — counting them once put a mega account'smedianViewsRecentat 0 while its average was 3M. riskSignals/credibilitySignals— the named signals behind the score, split by direction. Credibility signals move the score down and previously had no representation in the row at all.
Example row — full record (Profile Audit Rows view)
A real, unedited row produced by the actor for @instagram — run SnWN7C6dEaBsTa935, sourceActorVersion 1.4, scoreVersion 1.2.0. Every column in the groups above is present, with null where Instagram does not expose a value publicly. Only profilePicUrl is shortened here: it is a signed CDN URL several hundred characters long that expires.
{"inputUsername": "instagram","profileUsername": "instagram","profileUrl": "https://www.instagram.com/instagram/","status": "ok","errorCode": null,"errorMessage": null,"instagramUserId": "25025320","fullName": "Instagram","isVerified": true,"isPrivate": false,"accountType": "creator","categoryName": null,"profilePicUrl": "https://scontent-iad3-2.cdninstagram.com/v/t51.82787-19/550891366_18667771684001321_1383210656577177067_n.jpg?stp=dst-jpg_s320x320_tt6&...","hasDefaultProfilePic": false,"followersCount": 685833413,"followingCount": 256,"postsCount": 8525,"followersToFollowingRatio": 2679036.7695,"followersBucket": "mega","biography": "Discover what's new on Instagram 🔎✨","externalUrl": "https://www.youtube.com/watch?v=H6G9PcvHjj4","hasExternalUrl": true,"recentPostsAnalyzed": 9,"avgLikesRecent": 348422,"avgCommentsRecent": 12669,"avgViewsRecent": 9043556,"medianLikesRecent": 230586,"medianCommentsRecent": 9036,"medianViewsRecent": 7212744,"maxLikesRecent": 947589,"maxCommentsRecent": 32697,"latestPostTimestamp": "2026-07-16T19:04:50.000Z","daysSinceLatestPost": 0,"recentActivitySignal": "active","engagementRateApprox": 0.000526,"commentsToFollowersRatio": 0.000018,"likesToFollowersRatio": 0.000508,"viewsToFollowersRatio": 0.013186,"commentsToLikesRatio": 0.0364,"engagementVarianceHint": "moderate","lowEngagementFlag": true,"highFollowerLowEngagementFlag": false,"highFollowingFlag": false,"sparseContentFlag": false,"lowRecencyFlag": false,"inconsistentPerformanceFlag": false,"suspiciousGrowthProxyFlag": false,"profileCompletenessFlag": false,"defaultProfilePicFlag": false,"usernamePatternFlag": false,"postingRegularityFlag": false,"businessCredibilitySignal": true,"botRiskScore": 3,"botRiskBucket": "low","confidenceScore": 100,"confidenceBucket": "high","signalCount": 1,"riskSignalCount": 1,"credibilitySignalCount": 3,"evidenceCoverageScore": 100,"scoreVersion": "1.2.0","botRiskReason": "Engagement rate is below typical baselines for this follower tier. Some credibility signals are present and the account is verified, which partially offsets the risk. Review manually before partnership.","topFlags": "lowEngagementFlag","riskSignals": "lowEngagementFlag","credibilitySignals": "business_or_creator_account|verified_account|established_post_history","dataQualityWarnings": null,"isLikelyBot": false,"manualReviewPriority": "low","scrapedAt": "2026-07-17T11:56:47.744Z","runId": "SnWN7C6dEaBsTa935","sourceActorVersion": "1.4"}
How to read that row — a flag is not a verdict. lowEngagementFlag is true, yet botRiskScore is 3 and the bucket is low. Both are correct. At 685M followers a 0.053% engagement rate really is under par even for that tier, so the flag fires and says so. But the account is verified and shows credibility signals, which offset it — and the flag alone was never enough to convict. This is what "audit aid, not fraud detector" means in practice: you get the observation and the weighting, separately, so you can disagree with us and see exactly where.
The same run, for contrast (SnWN7C6dEaBsTa935, all four profiles):
| Profile | Followers | Engagement | Score | Bucket | Flags raised |
|---|---|---|---|---|---|
@nike | 291.8M | 0.284% | 0 | low | — |
@therock | 382.3M | 0.084% | 3 | low | lowEngagementFlag |
@instagram | 685.8M | 0.053% | 3 | low | lowEngagementFlag |
@tesla | 178 | — | 10 | unknown | private — not billed |
Three of the most scrutinised accounts on the platform, and the scores separate them by evidence rather than lumping them together. @tesla is a private personal account that happens to hold the handle — it returns a row with status: private, an honest unknown bucket, and no charge.
Heuristic flags
| Flag | Trigger (default thresholds) |
|---|---|
lowEngagementFlag | Engagement rate below the low band for the account's follower tier (see below) and followers ≥ 1k |
highFollowerLowEngagementFlag | Engagement rate below the critical band for the tier and followers ≥ 50k |
highFollowingFlag | Following > 5000 and followers/following < 1 |
sparseContentFlag | Posts < 5 and followers ≥ 1k |
lowRecencyFlag | No visible post in the last 90 days |
inconsistentPerformanceFlag | High variance (CV ≥ 1.5) across recent like counts |
suspiciousGrowthProxyFlag | Followers ≥ 10k, posts < 20, AND lowEngagementFlag is true |
profileCompletenessFlag | No bio and no external link |
defaultProfilePicFlag | Account still uses Instagram's default avatar |
usernamePatternFlag | Handle is ≥ 40% digits, ends in ≥ 6 digits, or contains ≥ 3 underscores |
postingRegularityFlag | Gaps between recent posts vary by ≤ 15% (CV ≤ 0.15) — machine-like timing |
businessCredibilitySignal | Business or creator account (this reduces risk) |
Engagement bands by follower tier
Engagement rate falls as an audience grows — a 300M-follower brand cannot reach the rate a 5k-follower account does. So the two engagement flags compare each account against its own size tier, not a single global cutoff:
| Followers | low band | critical band |
|---|---|---|
| 1k – 10k | < 1.0% | < 0.4% |
| 10k – 100k | < 0.8% | < 0.3% |
| 100k – 1M | < 0.6% | < 0.25% |
| 1M – 10M | < 0.4% | < 0.15% |
| 10M – 100M | < 0.2% | < 0.08% |
| 100M+ | < 0.1% | < 0.04% |
Below 1k followers the engagement flags never fire — the numbers are too small to be meaningful.
The bands sit deliberately below the typical range for each tier, so a flag means "clearly under par for this size", not merely "below average". They are set from published per-tier benchmarks rather than measured on a labelled corpus, and they are the most likely part of the scoring to be tuned — which is what scoreVersion is for.
On postingRegularityFlag: real accounts post irregularly. Measured against live accounts, posting-interval variation runs 0.79–2.04 — even for accounts that post daily. The 0.15 threshold only catches genuinely machine-timed intervals. It is still a weak signal (weight 8) and is phrased as "can indicate scheduling or automation", because a disciplined scheduler is not a bot.
On categoryName: this column is always null, and we would rather say so than let you wonder. Instagram does not send any category field to a logged-out session — the key is absent from the response entirely, not merely empty — and this Actor deliberately runs without login or cookies (see below), so there is no way to recover it. The column is retained so the output shape stays stable for existing integrations, and it will populate by itself if Instagram ever restores the field. Nothing in the scoring depends on it: no flag, offset, or confidence point is computed from a value we cannot obtain.
On defaultProfilePicFlag: hasDefaultProfilePic is tri-state — true, false, or null meaning we could not tell. Instagram does not always expose this, and an unknown never raises risk; it is disclosed in dataQualityWarnings instead.
All thresholds live in a single THRESHOLDS constant in src/instagramExtractor.js so they can be audited and tuned without searching the codebase. Bumping the heuristic logic must also bump SCORE_VERSION.
Score buckets
botRiskBucket | Range |
|---|---|
low | 0–24 |
moderate | 25–49 |
elevated | 50–69 |
high | ≥ 70 |
unknown | Private / unreachable / no public data |
Confidence ranges: low (< 40), moderate (40–69), high (≥ 70).
How to read risk and confidence together
A high-risk score on a low-confidence row is not the same as a high-risk score on a high-confidence row. Always read both columns together:
| Risk | Confidence | Suggested action |
|---|---|---|
| Low | High | Low-priority review — the evidence supports the low score |
| High | High | Manual review before partnership — the strongest signal this actor can give |
| High | Low | Gather more evidence; do not reject automatically — we simply couldn't see enough |
| Unknown | Low | Private or unavailable; no authenticity conclusion can be drawn |
The manualReviewPriority column encodes this table for you, so you can sort a shortlist by it directly.
Graceful degradation
- Private accounts emit a row with
isPrivate: true,botRiskBucket: "unknown", and confidence near 0. - Unreachable / non-existent usernames emit a row with
botRiskBucket: "unknown"and a clearbotRiskReason— they do not stop the crawler. - When the recent-posts feed is unavailable, ratio fields are
nullandengagementVarianceHintisnull. The score is still computed using the available signals, but flags that depend on engagement won't fire.
None of these rows are billed. See What one result represents.
How much does it cost?
Pay-per-event, with a single event: profile-analysis-result, charged once per successfully analyzed profile row.
| Profiles analyzed | Free plan ($3.00 / 1,000) | Business plan ($1.20 / 1,000) |
|---|---|---|
| 1 | $0.003 | $0.0012 |
| 10 | $0.03 | $0.012 |
| 100 | $0.30 | $0.12 |
| 500 (typical creator shortlist) | $1.50 | $0.60 |
| 1,000 | $3.00 | $1.20 |
The price per 1,000 drops with your Apify subscription tier. There is no run-start fee, no charge for private/unreachable rows, and no surcharge for sampling more recent posts — recentPostsLimit and every analysis toggle are included in the base event.
The actor also respects your Apify spending limit: when the configured limit is reached, the run stops gracefully without further charges.
Why no proxy, cookie, or login is required
Most Instagram tooling asks you to supply a proxy, or worse, to paste in your own sessionid cookie from a logged-in account. This actor asks for neither, and it never will.
- No login, no cookie. We never ask for your Instagram credentials or session, so your account is never exposed and can never be actioned for something this tool did.
- No proxy to configure or pay for. There is no proxy field in the input because there is no proxy in the architecture.
- No private data. The actor only reads what any logged-out visitor can see on a public profile. Private accounts return an
unknownrow, never an inferred one.
For agencies running audits on a client's behalf, this matters: there is no credential to mishandle, no residential-proxy line item, and nothing in the data path that a client's legal team would need to review.
Integrations
The output is a flat dataset, so it drops into normal tooling with no reshaping:
- Google Sheets / Excel / CSV — export directly from the dataset.
topFlagsis pipe-delimited specifically so it stays a single scalar cell in CSV. - Webhooks — trigger a downstream service on run completion via Apify's webhook settings.
- Scheduler — re-audit a creator roster on a cadence and diff
botRiskScoreover time (scoreVersiontells you whether a change is real or just our scoring moving). - API / MCP — call the actor from JavaScript, Python, or cURL, or wire it into an MCP client. See the API tab on the Apify Store page for ready-made snippets.
FAQ
Can this prove an account has fake followers? No — and be sceptical of anything that claims it can. Follower authenticity cannot be proven from public profile-level data. This actor screens for patterns associated with inauthentic audiences and tells you how confident it is. It is designed to prioritize your manual review, not to replace it.
Is it legal to analyze Instagram profiles this way? The actor reads only public data that any logged-out visitor can see, with no login and no private information. That said, we're not lawyers and this isn't legal advice — you are responsible for how you use the output, particularly under GDPR/CCPA if you store rows containing personal data. See Legality & compliance.
Do I need an Instagram login, cookie, or proxy? No. None of the three. See Why no proxy, cookie, or login is required.
What happens with private, deleted, or renamed accounts?
You get a row with botRiskBucket: "unknown", a botRiskReason explaining what happened, and no charge. The run continues.
How accurate is the bot-risk score? It is a heuristic, not a classifier trained on labelled fraud, and we don't publish an accuracy figure because we'd have no honest way to compute one. What we do instead is publish every threshold (see Heuristic flags) so you can audit exactly why any given score came out the way it did — and pair it with a confidence score so you know how much evidence was behind it.
Can I export to Google Sheets or Excel? Yes. The output is deliberately flat with no nested objects or arrays. Export as CSV/Excel from the dataset, or connect Sheets via Apify's integrations.
What does confidence mean, and why is it separate from risk? Risk answers "how suspicious do the signals look?" Confidence answers "how much evidence did we actually have?" A brand-new account with two posts can look suspicious on paper while giving us almost nothing to go on — that's high risk, low confidence, and it deserves a very different response than the same score backed by 24 posts. Keeping them separate is the whole point; collapsing them into one number is how other tools produce confident-sounding nonsense.
Limitations
- All signals are heuristic and based only on public profile data. The actor does not, and cannot, prove fake followers.
- Instagram's public surface evolves; some metric fields can be missing for some accounts. The actor handles this by lowering the confidence score rather than failing.
- Aggressive sampling will trigger Instagram rate limits — keep
recentPostsLimitmodest (maxConcurrencyis held at 1 internally). - Engagement baselines vary by niche and follower tier. A low engagement rate is a reason to look closer, not a verdict.
- The actor analyzes profile-level signals only. It does not inspect individual followers, and it never will — that is a different product with different privacy implications.
Legality & compliance
- Public data only. The actor reads what a logged-out visitor sees on a public profile. It does not access private accounts, follower lists, DMs, or any authenticated surface.
- No credentials, ever. No Instagram login or
sessionidcookie is requested, stored, or transmitted. Your account is never involved. - No accusations. Output is framed as risk and confidence with named reasons. The actor deliberately emits no
isFraud-style verdict and no "% fake followers" claim, because public data does not support either. - Your responsibility. Output rows can contain personal data (username, bio, follower counts). If you store or process them, you are the data controller under GDPR/CCPA and it's on you to have a lawful basis, honour retention limits, and respond to subject requests. Nothing here is legal advice.
- Intended use. Influencer vetting, creator screening, and research. Not for harassment, targeting of individuals, or any automated decision that materially affects someone without human review.
Changelog
Scoring changes are tracked separately from code changes. scoreVersion only moves when the heuristics move, so you can always tell whether a score changed because the account changed or because we did.
| Actor version | scoreVersion | Changes |
|---|---|---|
| 1.4 | 1.2.0 ⚠️ | Scores are NOT directly comparable with 1.1.0 runs — large accounts score materially lower. Engagement-rate cutoffs are now per follower tier instead of one flat 0.5% line for every account above 1k followers. A flat cutoff no engagement rate can clear at scale meant large legitimate accounts were all flagged: @instagram, @therock and @nike previously scored an identical 25 "moderate"; they now score 3, 3 and 0 — "low". Small accounts are unaffected in practice. The full band table is published under Engagement bands by follower tier. Fixed: view metrics no longer count photo posts as "zero views" (this put medianViewsRecent at 0 on accounts whose videos have millions of views), and thin_views_sample now discloses when fewer than 3 videos back those figures. categoryName is documented as permanently null and removed from all scoring paths — Instagram sends no category to anonymous sessions (no score impact: the terms were unreachable). Internal: the actor no longer runs a browser — requests use browser-fingerprinted HTTP, cutting run time from minutes to seconds. usernames and profileUrls are merged into one input field. The Console form is down to 5 fields (from 10): usernames, maxProfiles, analysisDepth, recentPostsLimit, minPostsForFullConfidence. Timeout / concurrency / debug became internal tuned settings; profileUrls and minVisiblePostsRequired are superseded; and the three include* switches are always on now, with any value sent for them ignored. ⚠️ If you previously set includeDerivedSignals: false, your scores will change — and the old ones were wrong. That switch gated the engagement evidence the score is built from, so a suspicious profile could come back 10 "low" / isLikelyBot: false at full confidence when it should have been 68 "elevated". Runs with it left at the default are unaffected. All eight hidden inputs remain accepted over the API, so no saved task or integration breaks — nothing was removed from the input contract. |
| 1.3 | 1.1.0 ⚠️ | Scores are NOT directly comparable with 1.0–1.2 runs. Three new signals: defaultProfilePicFlag (+10), usernamePatternFlag (+8), postingRegularityFlag (+8) — all computed from data we already fetched, no extra requests. New fields: profilePicUrl, medianViewsRecent, riskSignals / credibilitySignals (+ counts), evidenceCoverageScore, dataQualityWarnings, isLikelyBot. Fixed: topFlags and botRiskReason now order by weight (previously declaration order, so the strongest reason could be dropped); the verified offset is now named when credibility is also present (previously silently omitted, explaining −10 of a −25 move). Billing now reports what was actually charged. |
| 1.2 | 1.0.0 (unchanged) | Internal only: added a regression test suite, consolidated the two row builders into one factory, and moved the scoring weights into an auditable table. Provably no behavior change — scores are identical to 1.0 and 1.1 runs. |
| 1.1 | 1.0.0 (unchanged) | Added maxProfiles cost guard-rail (default 100). usernames now accepts full profile URLs and @-prefixed handles. Renamed minVisiblePostsRequired → minPostsForFullConfidence (old name still accepted). Documentation overhaul: FAQ, legality, pricing examples, risk×confidence matrix. No scoring changes — scores remain directly comparable with 1.0 runs. |
| 1.0 | 1.0.0 | Initial release. |
Five actor releases, three scoreVersions — and that is exactly the point of publishing both. Across 1.0 → 1.2 you can upgrade and know, without testing it yourself, that any score which moved did so because the account changed, not because we did. At 1.3 and again at 1.4 the scoring genuinely changed, and we are telling you so in a machine-readable field rather than letting you discover it in a diff. The 1.4 change is the kind that is easiest to hide and worst to hide: it moves scores down, on the largest accounts, which is exactly where a quiet recalibration would flatter us.
If you track creators over time, filter on scoreVersion before comparing two runs. No competitor in this category publishes this, which means with any of them you simply cannot tell the two cases apart.
Upgrade note for existing users:
maxProfilesdefaults to 100. If you currently submit more than 100 profiles in a single run, setmaxProfilesexplicitly (up to 1000) to keep the previous behaviour. The run log states clearly whenever the cap truncates a list.
Local development
cd actornpm installnpm start # uses INPUT.jsonnpm run dev # local storage only
The default INPUT.json analyzes a single well-known public profile so first runs are quick.
Support & author
Built and maintained by Coregent Devspace.
- Bug, blocker, or a profile that scored oddly? Open an issue on the Apify Store page's Issues tab. Include the run ID and the handle —
runIdis on every output row for exactly this reason. - Feature requests are welcome, with one standing exception: follower-by-follower auditing is deliberately out of scope for this actor.
- If this saved you time, a rating on the Store page genuinely helps.