# Instagram Mentions Scraper - Tagged Posts, $0.44/1k (`dami_studio/instagram-mentions-scraper`) Actor

Find every public Instagram post that tags or mentions a handle: post link, caption, author, likes, comments, date and image URLs. The cheapest Instagram mentions scraper on the market. No login, no cookies, no browser.

- **URL**: https://apify.com/dami\_studio/instagram-mentions-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Social media, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.44 / 1,000 mention scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Instagram Mentions & Tagged Posts Scraper

Give it an Instagram handle and get back the public posts that OTHER people made about it — tagged in the photo, named in the caption, or credited as a co-author. Each row is one post: the link, the caption, who posted it, likes, comments, when it went up, the image URLs and which of your accounts it mentions. No browser, and nothing for you to set up.

- Reads Instagram's own server-side mentions index for the handle, so you are not paying to sift a profile feed for the handful of posts that happen to mention someone.
- Tells you HOW each post mentions the account — tagged in the photo, named in the caption, or credited as a co-author — in a `mentionType` field you can filter on.
- Watch many handles in one run. A post that mentions two of your handles is returned once per handle, because that is two different findings.
- Runs with empty input return a labelled sample row, free, so you can see the shape first.
- Nothing to configure. Handles are read without an account, and a built-in session covers the handles Instagram will not identify to a logged-out reader.
- A handle that stays unreadable comes back as a free diagnostic row, never as a charged one.

### Price

**$0.44 per 1,000 mentions**, plus a **$0.0005 start fee per run** (billed per gigabyte of run memory, so exactly that on the default 1 GB).

This is **the cheapest Instagram mentions scraper on the market**, and it is the same rate on every plan, free or paid. There are no volume tiers, no minimum spend, no subscription and no add-on fees. What you read here is what you pay on day one and on day four hundred.

| Mentions | Total cost |
|---|---|
| 100 | $0.0445 |
| 1,000 | $0.4405 |
| 10,000 | $4.4005 |
| 100,000 | $44.0005 |

#### What is actually charged

- **One `mention-scraped` event per mention row written to the dataset.** Nothing else is metered per row.
- **Free:** the sample row an empty run returns, and every diagnostic row — a blocked target, a dead URL, a search that matched nothing. Those rows all carry `"charged": false`.
- Duplicate posts already returned for the same handle earlier in the run are dropped before they are charged.
- Handles that do not exist, are hidden from logged-out visitors, or have nobody tagging them produce an uncharged diagnostic row instead of a bill.
- Posts filtered out by `onlyTagged` are never charged — the filter runs before billing.
- A run that finds nothing costs the start fee and nothing more.
- Rows never leave the dataset without a charge, and are never charged without a row. The billed event is a named one, so there is no price quietly attached to `apify-default-dataset-item` — the trick that makes some scrapers bill you for their own error messages.

### Input

```json
{
  "usernames": [
    "redbull",
    "gopro"
  ],
  "maxItems": 60,
  "resultsPerUsername": 30
}
```

| Field | What it does |
|---|---|
| `usernames` | The Instagram accounts you want mentions of. Up to 200 per run. A plain handle, `@handle` and a full profile URL all work — `redbull`, `@redbull` and `https://www.instagram.com/redbull/` are the same input. |
| `maxItems` | Total rows to return across every handle. Default 100, hard ceiling 10,000. This is the number you pay for, so keep it low while you are testing. |
| `resultsPerUsername` | Optional per-handle cap. Leave it empty and `maxItems` is shared evenly between your handles; set it and each handle stops at this many posts (still bounded by `maxItems` overall). |
| `onlyTagged` | Off by default. Turn it on to keep only posts where the account is tagged in the photo or credited as a co-author, and drop posts that merely name it in the caption. |
| `concurrency` | How many handles to read at once. Default 4, maximum 8. Raise it for a long list; lower it if you are supplying your own proxy servers and they are few. |
| `sessionCookies` | Leave empty. Runs work out of the box using a built-in session for the handles that need one. That session is shared, so on a long list under heavy load some handles can come back blocked; putting your own Instagram cookie here gives the run a rate limit nobody else shares. Paste it as `sessionid=...; csrftoken=...` — Chrome: F12 → Application → Cookies → instagram.com. It is used for this run's requests and nothing else. |
| `proxyUrls` | Leave empty. Fill it in only if you want the traffic to leave through proxy servers you already pay for, as `http://user:pass@host:port`. |

Run it with **empty input** and you get one clearly labelled sample row, free, so you can see the output shape before you spend anything.

### Output

One row per mention. A real row from a real run:

```json
{
  "ok": true,
  "charged": true,
  "recordType": "mention",
  "mentionedUsername": "redbull",
  "mentionedUserId": "476322",
  "mentionType": "caption-mention",
  "mentionTypes": [
    "caption-mention"
  ],
  "url": "https://www.instagram.com/p/B3M0F6ODksj/",
  "shortCode": "B3M0F6ODksj",
  "postId": "2147320226877688611",
  "type": "image",
  "caption": "Ducati V4R x @redbull built by @hvmotorcycles 🔥🔥@ducatiusa",
  "hashtags": [],
  "mentions": [
    "redbull",
    "hvmotorcycles",
    "ducatiusa"
  ],
  "ownerUsername": "2wheeltv",
  "ownerId": "4516500559",
  "likesCount": 5403,
  "commentsCount": 25,
  "viewsCount": null,
  "timestamp": "2019-10-04T14:49:00.405Z",
  "timestampIsApproximate": true,
  "displayUrl": "https://scontent.cdninstagram.com/v/t51.82787-15/628070032_18391485244194380_n.jpg",
  "images": [
    "https://scontent.cdninstagram.com/v/t51.82787-15/628070032_18391485244194380_n.jpg"
  ],
  "taggedUserIds": [],
  "coauthorUsernames": [],
  "isVideo": false,
  "productType": "feed",
  "carouselCount": 0,
  "dimensions": {
    "width": 1121,
    "height": 1121
  },
  "accessibilityCaption": null,
  "inputUrl": "https://www.instagram.com/redbull/tagged/",
  "scrapedAt": "2026-08-16T05:12:33.953Z"
}
```

#### Field notes

- `mentionedUsername` — which of YOUR handles this post is about. It is the join key when you watch several accounts in one run.
- `mentionType` — why the post is in the index: `photo-tag` (the account is tagged in the image), `caption-mention` (the caption names it with an @), `coauthor` (the account is a credited co-author), or `tagged` when Instagram put the post in the index but the public payload does not say which of the three applies. `mentionTypes` carries all that apply.
- `ownerUsername` — the account that PUBLISHED the post — the person talking about you, not you.
- `caption` — the full caption as written, newlines and emoji intact. `hashtags` and `mentions` are parsed out of it for convenience.
- `likesCount / commentsCount` — a snapshot at read time, not at publication. They keep moving.
- `viewsCount` — video view count where Instagram publishes one for a logged-out viewer. It is often null, including on videos — see the limitations.
- `timestamp` — when the post went up, in ISO 8601 UTC, derived from the post id. It is accurate to the minute for most posts and always lands on the correct calendar date; `timestampIsApproximate` is true on every row to keep that explicit.
- `displayUrl / images` — the largest still image Instagram serves for the post. A carousel lists one image per slide in `images`; a video or reel gives its cover frame. These are Instagram CDN links and they expire after a while, so download what you need promptly.
- `taggedUserIds` — the numeric ids of every account tagged in the media, when the public payload carries them. Ids only — this surface does not publish their handles.
- `url` — the canonical post link. Reels get a `/reel/` link, everything else `/p/`.

Every real row carries `"charged": true`. Sample rows carry `"_sample": true` and diagnostic rows carry `"_diagnostic": true` with an `errorCode` you can filter on, and neither is ever billed.

### How it works

- Instagram keeps a server-side index of the posts that tag or mention an account — the same one behind the Tagged tab on a profile. This Actor asks that index directly, so the work is proportional to the mentions you want rather than to the size of anybody's feed.
- A handle is first resolved to its numeric account id through Instagram's own public profile card, with the fuller public profile record as a fallback for the rare handle the card will not serve.
- Results are read a page at a time and followed with the cursor Instagram hands back, until your row limit is reached or the index runs out.
- Requests leave through a large pool of rotating datacenter addresses. Instagram meters these surfaces per address, so a refusal is answered by moving to another address immediately rather than by waiting, and addresses are retired before they hit their limit.
- Nothing is rendered in a browser — it is plain HTTP against public endpoints, which is what keeps the price where it is.
- Every handle is tried logged-out first, because the Tagged tab is part of a public profile page and normally answers an anonymous reader on the first attempt. A built-in session is the fallback for the two cases that measurement showed no address can reach: a handle Instagram will not identify anonymously, and an index that refused every address the run tried.

### What people use it for

- Brand monitoring: run your own handle daily and see every public post that tagged or named you since yesterday, with the engagement each one got.
- Finding creators and advocates: sort a run by `likesCount` and you have the accounts putting your brand in front of the biggest audiences, ranked, with links to the posts.
- User-generated content sourcing: `mentionType: "photo-tag"` plus `images` gives you a clean shortlist of photos featuring your product, with the author handle to ask permission.
- Campaign and event reporting: pull mentions of the campaign account for a date range and total the likes and comments to get earned reach without a social suite subscription.
- Competitive research on public accounts: watch several handles at once and compare who is being tagged more, by whom, and with what engagement.
- Influencer verification: check that a creator actually tagged the brand they invoiced for, and see what the post did.

### Reading the output

Every run writes three kinds of row, and they are easy to tell apart:

- **Real rows** carry `"charged": true` and `"recordType": "mention"`. These are the rows you paid for, one billed event each.
- **The sample row** carries `"_sample": true` and `"charged": false`. There is exactly one, it only appears when the input had no usernames, and it exists so you can look at the shape of the output before you spend anything.
- **Diagnostic rows** carry `"_diagnostic": true`, `"charged": false`, the handle they belong to in `mentionedUsername`, and an `errorCode` you can switch on: `NOT_FOUND` when a handle cannot be read without an account, `NO_RESULTS` when nobody has publicly tagged or mentioned it, `RATE_LIMITED` or `BLOCKED` when Instagram refused the index from every address the run tried, `TIME_BUDGET` when the run ran out of time before reaching a handle, and `BAD_INPUT` when the whole list turned out to be unreadable.

If you only want the data, filter on `charged == true`. The count of those rows always equals the number of events you were billed for, so the dataset is its own invoice.

### How this differs from a profile posts scraper

A profile scraper answers "what did this account post". This one answers the opposite question — "what did everyone else post about this account" — and the two barely overlap. If you tried to get mentions by pulling a brand's own feed and searching the captions, you would read hundreds of rows to find none of them, because the posts you want were written by other people entirely. That is the whole reason this Actor exists as its own thing: it reads the index Instagram already keeps for exactly this question, so you pay for mentions rather than for the search.

### Limitations

- Public accounts only. A private account's tagged posts are not public, and nothing here logs in to see them.
- Some public accounts are hidden from logged-out visitors entirely — age-restricted brands are the usual case, and a handful of others behave the same way. Those handles are retried with the built-in session; the ones it does not open either return an uncharged `NOT_FOUND` diagnostic row rather than data. Measured logged-out on an 80-handle sample, this affected roughly one handle in eleven, and it is a property of the account rather than of the run, so a plain retry will not change it.
- The built-in session is shared, and Instagram rate-limits per account rather than per caller, so it is not unlimited throughput. Under heavy concurrent load supply your own cookie in `sessionCookies` for a rate limit that is yours alone. If the built-in session runs out mid-run, the run finishes with what it collected and says so on its status line rather than failing.
- An account can hide its own tagged tab, and its owner can remove individual posts from it. Anything removed there is invisible here too — this reflects what Instagram publishes, not everything ever posted.
- Timestamps are derived from the post id, not read from a publish clock. Measured against Instagram's own value on 48 posts: half were within 33 seconds, nine in ten within two and a half minutes, the worst was just under nine minutes, and every one landed on the correct date. Use them for ordering and for date filtering; do not use them to prove a post went up at an exact second.
- No video files. This surface publishes cover images for videos and reels, not the MP4, so `images` holds stills only. `viewsCount` is frequently null for the same reason.
- Author details are the handle and the numeric id. Follower counts, display names and verification badges are not part of this payload — run a profile scraper over `ownerUsername` if you need them.
- Comments are counted, not returned.
- Image URLs are signed Instagram CDN links and stop working after a while. Download anything you intend to keep during or shortly after the run.
- The index is ordered by Instagram, not strictly by date, and it mixes recent and old posts. Ask for more rows and sort on `timestamp` yourself if you need a strict window.
- Hard ceilings are 10,000 rows and 200 handles per run. Split larger jobs across runs.
- Instagram meters these endpoints per address. A very large run can hit a stretch where every address is refused; that produces uncharged `RATE_LIMITED` rows for the affected handles and the run still finishes as succeeded.

### Questions

**Does this find posts that only mention the account in a comment?**

No. It covers posts that tag the account in the image, name it with an @ in the caption, or credit it as a co-author — the same set Instagram itself shows on the Tagged tab. Comments are a different surface and are out of scope.

**What happens if a handle has no mentions at all?**

You get one uncharged diagnostic row for that handle with `errorCode: "NO_RESULTS"`, and the run carries on to your other handles. You are never billed for a handle that returned nothing.

**Do I need an Instagram account or cookies?**

No. Runs work out of the box with nothing filled in. Handles are read the way an anonymous visitor reads a Tagged tab, and the handles Instagram will not identify that way fall back to a built-in session.

**Then what is `sessionCookies` for?**

A private rate limit. The built-in session is shared between runs, and Instagram counts requests per account rather than per caller, so a busy hour is a slower hour for everyone using it. Paste your own Instagram cookie into `sessionCookies` and this run uses only that — its own quota, nobody else's. It is used for the run's requests and nothing else: never stored, never logged, never written into the dataset. Optional, and most runs never need it.

**Do I need a proxy?**

No. The run brings its own egress and its cost is already inside the price you see. The `proxyUrls` field exists only for callers who specifically want traffic to leave through servers they already own.

**Will the run fail if Instagram blocks it?**

No. A blocked, empty or unreadable handle produces an uncharged diagnostic row explaining what happened, and the run still finishes as succeeded. A failed run would still bill you the start fee, which would mean paying to be told something went wrong.

**Can I run this on a schedule?**

Yes, and that is the main way people use it. Nothing is held between runs, so the same input is safe to repeat. Diff on `postId` to see only what is new since last time.

**How do I get exactly the rows I paid for?**

Filter the dataset on `"charged": true`. Sample and diagnostic rows are always `false`, and the number of charged rows always equals the number of billed events.

**Why is the same post in my results twice?**

It is not — check `mentionedUsername`. If a post tags two of the handles you asked about, you get one row per handle, because for reporting purposes that is a mention of each. Within a single handle, duplicates are removed before anything is charged.

# Actor input Schema

## `usernames` (type: `array`):

The accounts you want mentions of. A plain handle, @handle or a full profile URL all work. Up to 200 per run.

## `maxItems` (type: `integer`):

Total posts to return across every handle. This is the number you pay for, so keep it low while testing.

## `resultsPerUsername` (type: `integer`):

Cap the posts returned for each handle. Leave empty and the row limit above is shared evenly between your handles.

## `onlyTagged` (type: `boolean`):

Keep only posts where the account is tagged in the image or credited as a co-author, and drop posts that merely name it in the caption. Filtered posts are never charged.

## `concurrency` (type: `integer`):

How many handles to read in parallel. Raise it for a long list; lower it if you supply your own proxy servers and they are few.

## `sessionCookies` (type: `array`):

Leave this empty. Tagged posts are read without an account, and a built-in session is only used for the handles Instagram will not identify to a logged-out reader — age-restricted brand accounts are the usual case. That built-in session is shared between runs, so if you are working a long list and handles come back blocked, paste your own Instagram cookie here and this run gets a rate limit nobody else shares. In Chrome: F12 → Application → Cookies → instagram.com. The `sessionid` cookie is the one that matters; `csrftoken` alongside it is better. Paste it as `sessionid=...; csrftoken=...`, one entry per account. Cookies are used for this run's requests and nothing else — never stored, never logged, never written to the dataset.

## `proxyUrls` (type: `array`):

Leave this empty. By default the run rotates a large pool of addresses that cost you nothing per gigabyte. Fill it in only if you specifically want the traffic to leave through proxy servers you already pay for, in the form http://user:pass@host:port.

## Actor input object example

```json
{
  "usernames": [
    "redbull",
    "gopro"
  ],
  "maxItems": 100,
  "onlyTagged": false,
  "concurrency": 4
}
```

# Actor output Schema

## `results` (type: `string`):

Every row in the default dataset: mentionedUsername, mentionType, url, ownerUsername, caption, likesCount, commentsCount, timestamp, type, displayUrl, viewsCount, shortCode, mentionedUserId, ownerId, postId, mentionTypes, images, hashtags, mentions, taggedUserIds, coauthorUsernames, isVideo, productType, carouselCount, dimensions, accessibilityCaption, timestampIsApproximate, inputUrl, scrapedAt. An empty, blocked or unmatched run returns a single uncharged row explaining what happened instead.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "usernames": [
        "redbull",
        "gopro"
    ],
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/instagram-mentions-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "usernames": [
        "redbull",
        "gopro",
    ],
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/instagram-mentions-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "usernames": [
    "redbull",
    "gopro"
  ],
  "maxItems": 100
}' |
apify call dami_studio/instagram-mentions-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/instagram-mentions-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/ryaoAShTwBZZPgHb6/builds/2JNvqtkdSNw9OgjWT/openapi.json
