Made by Desearch (desearch.ai).
Instagram Engagement Rate Calculator is a small Python script for marketers. Give it one public Instagram username and it pulls the profile and the latest posts, then writes the engagement rate with the formula shown and a split by format (image, carousel, video/reel). You can add one or two peer accounts. Each account is calculated on its own.
The problem
A single viral post can make an account look more active than its recent feed is. Format matters too: a carousel and a video do not collect likes the same way. Copying likes, comments and follower counts out of a profile by hand is slow, and a pinned post or a post from the last day can skew the number. What helps is the formula written out, the mean next to the median, and a split by format.
What the app does
- Takes one public Instagram username, and optionally one or two peers.
- Pulls the follower count and the latest posts (12 in the sample, capped at 20 in the script).
- Computes a per-post engagement rate, then the mean and the median for the account.
- Leaves out likely pinned posts, posts under 24 hours old, and posts with hidden likes, and counts what it skipped.
- Splits the same formula by image, carousel and video/reel.
- Writes a Markdown summary, three CSVs and a bar chart of engagement rate by format.
How it works
- Profile.
GET /desearch/instagram/profile/{username}for the follower count. - Posts.
GET /desearch/instagram/profile/{username}/postswithcountfor the latest posts. Each post can includelikeCount,commentCount,mediaTypeandcreateTime. - Skip. Likely pinned posts (listed first but older than the post after them), posts under 24 hours old, and posts with hidden likes stay out of the rate and are counted in the output.
- Rate. Per post: engagement rate % = (likes + comments) / followers x 100. The account rate is the mean of those rates. The median is shown next to it.
- Write. A summary, a posts CSV, a formats CSV and an SVG chart. Saves and shares are not in the data, so this is a likes plus comments rate.
The code
The script reads the API key only from a key file (~/.desearch_api_key by default, or --key-file PATH). It does not read environment variables. The request, copied from ig_engagement_rate.py on main (lines 49 to 63):
def call(session, path, params, label):
at, t0 = now(), time.time()
try:
r = session.get(BASE + path, params=params, timeout=120)
status, hdr = r.status_code, {k.lower(): v for k, v in r.headers.items()}
try: body = r.json()
except ValueError: body = {"_text": r.text[:3000]}
except Exception as e: # network error: keep going, record it
status, hdr, body = None, {}, {"_error": repr(e)}
rec = {"label": label, "path": path, "params": params, "at": at, "seconds": round(time.time() - t0, 2),
"status": status, "cost_usd": hdr.get("x-desearch-cost-usd"), "usage_count": hdr.get("x-desearch-usage-count"),
"headers": {k: v for k, v in hdr.items() if k.startswith("x-desearch") or k == "date"}, "body": body}
n = len(body) if isinstance(body, list) else (1 if status == 200 else 0)
print(f"{at} {label:28s} {status} {rec['seconds']:6.2f}s items={n} cost={rec['cost_usd']}", file=sys.stderr, flush=True)
return recThe per-post rate, from the same file (lines 119 to 127):
def post_row(account, followers, p, ref_time=None):
s = p.get("stats") or {}
likes, com = count_or_none(s.get("likeCount")), count_or_none(s.get("commentCount"))
views = count_or_none(s.get("viewCount")) or count_or_none(s.get("playCount"))
t = parse_time(p.get("createTime"))
f, raw = fmt_of(p)
hidden = likes is None
er = round(100 * (likes + (com or 0)) / followers, 4) if followers and not hidden else None
epv = round(100 * (likes + (com or 0)) / views, 3) if views and not hidden else NoneThe folder has ig_engagement_rate.py, requirements.txt, samples/sample-summary.md, samples/live-run-2026-10-17.md and offline tests in tests/.
Endpoints
GET /desearch/instagram/profile/{username}GET /desearch/instagram/profile/{username}/posts
Example output
Saved sample from a run at 2026-10-17T09:27:12+04:00 (09:27 Asia/Tbilisi) on three public brand accounts, shown as brand_a, brand_b and brand_c. This page shows that stored response. It does not run a live demo.
Formula. Per post: engagement rate % = (likes + comments) / followers x 100. Account rate = the mean of the post rates. The median is shown next to it.
| account | followers | posts used / returned | engagement rate % | median % | range % | best format |
|---|---|---|---|---|---|---|
| brand_a | 268356497 | 6 / 12 | 0.0558 | 0.0423 | 0.0014 to 0.1699 | carousel (0.0858 %, n=3) |
| brand_b | 104375766 | 8 / 12 | 0.4525 | 0.2474 | 0.0527 to 1.8016 | carousel (0.8143 %, n=3) |
| brand_c | 17566964 | 12 / 12 | 0.3943 | 0.2972 | 0.0973 to 1.0892 | video/reel (0.4657 %, n=8) |
From sample-summary.csv, brand_a skipped 3 likely pinned posts and 3 posts under 24 hours (0 hidden likes). brand_b skipped 3 likely pinned posts and 1 post under 24 hours (0 hidden likes). brand_c skipped none. These are rates on the recent posts Desearch returned on 2026-10-17, not benchmarks.
By format
| account | format | posts | avg likes | avg comments | avg views | engagement rate % | median % |
|---|---|---|---|---|---|---|---|
| brand_a | carousel | 3 | 229951.3 | 341 | 0.0858 | 0.0636 | |
| brand_a | image | 1 | 162007 | 637 | 0.0606 | 0.0606 | |
| brand_a | video/reel | 2 | 22661.5 | 87 | 0.0085 | 0.0085 | |
| brand_b | carousel | 3 | 847052 | 2884.7 | 0.8143 | 0.5454 | |
| brand_b | image | 4 | 291893 | 1585 | 0.2812 | 0.2474 | |
| brand_b | video/reel | 1 | 54373 | 590 | 3250761 | 0.0527 | 0.0527 |
| brand_c | carousel | 2 | 29873 | 282.5 | 0.1716 | 0.1716 | |
| brand_c | image | 2 | 57538 | 656 | 0.3313 | 0.3313 | |
| brand_c | video/reel | 8 | 80984.6 | 830 | 0.4657 | 0.3393 |
Each posts call returned the number of posts asked for (12 of 12 for three accounts). The call log in samples/live-run-2026-10-17.md:
| call | status | seconds | posts returned |
|---|---|---|---|
GET /desearch/instagram/profile/brand_a | 200 | 9.57 | |
GET /desearch/instagram/profile/brand_a/posts count=12 | 200 | 11.87 | 12 |
GET /desearch/instagram/profile/brand_b | 200 | 6.64 | |
GET /desearch/instagram/profile/brand_b/posts count=12 | 200 | 11.25 | 12 |
GET /desearch/instagram/profile/brand_c | 200 | 5.81 | |
GET /desearch/instagram/profile/brand_c/posts count=12 | 200 | 11.63 | 12 |
Run it yourself
git clone https://github.com/Desearch-ai/desearch-py-examples && cd desearch-py-examples/instagram-engagement-rate-calculator
pip install -r requirements.txt
printf '%s' 'YOUR_KEY' > ~/.desearch_api_key && chmod 600 ~/.desearch_api_key # key from console.desearch.ai
python ig_engagement_rate.py --accounts BRAND_ONE --count 12 --out output/brandWith two peers: python ig_engagement_rate.py --accounts BRAND_ONE PEER_ONE PEER_TWO --count 12 --out output/brands.
Useful flags: --count (posts per account, capped at 20), --include-pinned, --include-fresh, --raw-out, --key-file. To try it with no key and no cost: python ig_engagement_rate.py --from-raw samples/sample-raw.json --out output/sample.
What it costs
Instagram calls are priced per returned item. The price for each call is in the X-Desearch-Cost-Usd response header, and the script prints that header per call and as a sum. The saved sample does not include those header values, so this page does not state a dollar amount. See pricing.
Ideas to extend it
- Add one or two peer accounts and compare the format split.
- Re-run the same accounts next month and compare the rates.
- Pass
--include-pinnedor--include-freshwhen you want those posts in the rate.
