# Instagram Profile Scraper: Posts and Reels (`accountable_eel/ig-profile-scraper`) Actor

Instagram scraper for public profiles, no login. Give it usernames, get one row per post or reel: caption, hashtags, mentions, likes, comments, views, video length, video file, audio track, tagged accounts and location, plus an optional profile row. A profile that returns nothing is free.

- **URL**: https://apify.com/accountable\_eel/ig-profile-scraper.md
- **Developed by:** [Adrian Voss](https://apify.com/accountable_eel) (community)
- **Categories:** Social media, Marketing
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.64 / 1,000 post returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Instagram Profile Scraper: Posts and Reels

One row per Instagram post, without a login. Give this actor a list of public usernames and it
returns a flat row per post or reel: caption, hashtags, mentions, likes, comments, views, video
length, the video file, the audio track, tagged accounts and location, plus an optional profile
row with followers, bio and bio link. Field names are frozen and designed to drop straight into a
Sheet, a Clay table, an n8n workflow or an AI agent with no post-processing.

### Who it's for

A social analyst benchmarking a brand against three competitors wants the same twenty columns for
every account, every week, in one table - not four browser tabs and a screenshot. An influencer-
marketing team vetting creators before a deal needs the last dozen posts with real engagement
numbers attached, so they can see whether a 200k-follower account actually gets 200 likes. A
content team auditing its own hashtag strategy wants every hashtag and @mention its account has
used recently, counted. And an agent or workflow that already knows which accounts matter needs a
single HTTP call that answers "what did they post, and how did it do" in a shape it can read.

All four get the same thing here: usernames in, one flat row per post out, priced per post
returned, with a profile row that costs nothing.

### Why this one

- **The row shape is the product.** Every field name is frozen: `shortcode`, `posted_at`,
  `like_count`, `view_count`, `hashtags`, `audio`, `tagged_users`, `carousel_children`. They do not
  change between runs, which is what makes this safe to point a scheduled workflow or an AI agent
  at. The full untouched source objects come along in `raw` for anything this actor does not
  surface as its own column.
- **Listing and downloading are separate jobs, and this one is listing.** You get `video_url`,
  `display_url` and `thumbnail_url` as direct links. Nothing is downloaded, nothing is re-hosted,
  and you are not billed for bytes you did not want. Fetch the media yourself, for the posts you
  actually chose.
- **Two detail levels, priced apart.** "Basic" reads only each post's embed page; "Full" also reads
  the post's own page for the exact posting time, tagged accounts, location and collaborators, and
  moves about 1.7x the data. They are billed as two separate events rather than one blended price,
  so a "Basic" run is genuinely cheaper and not just faster.
- **A cheap date filter that runs before you pay.** "Only posts since" is answered from the free
  listing data, so posts older than your cut-off are dropped before any per-post request is made.
  They cost you nothing.
- **Honest about the logged-out ceiling.** Instagram shows a signed-out visitor 12 posts per tab
  and no more. This actor reads both the posts grid and the reels tab and merges them, which is
  the most anyone gets without an account, and it tells you in the run's summary when your limit
  could not be filled. See "What you can and cannot get without logging in" below.
- **Never charged for a miss.** A username that does not exist, a private account, a profile whose
  posts were all filtered out - each returns a row explaining what happened, and costs nothing.

### What you get

One row per post by default (turn off "Expand rows" in Advanced to get one row per username
instead, with everything nested under `posts`). With "Include a profile row" on, each username
also gets one `kind: "profile"` row, which is never billed.

| Field | Type | Row | Description |
|---|---|---|---|
| `query` | text | both | The username you submitted, echoed back |
| `found` | boolean | both | `true` if Instagram served a readable profile |
| `status` | text | both | `OK` on a match; `NOT_FOUND`, `BLOCKED` or `BAD_FORMAT` on a miss |
| `message` | text | both | Plain-English reason for a miss - only present when `found` is `false` |
| `username` | text | both | The account, normalised to a bare lowercase name |
| `postCount` | number | both | Post rows returned and billed for this username |
| `truncated` | boolean | both | `true` if more posts matched than your limit allowed |
| `detailCount` | number | both | Post rows that got the Full-detail second request, and were charged the surcharge |
| `kind` | text | both | `profile` or `post` |
| `ig_user_id` | text | both | Instagram's own numeric user ID |
| `full_name` | text | profile | Display name |
| `bio` | text | profile | Biography text |
| `followers` | number | profile | Exact follower count |
| `following` | number | profile | Exact following count |
| `posts_count` | number | profile | Total posts on the account, as Instagram displays it. Instagram itself rounds this on large accounts ("30K"), so treat it as approximate above a few thousand |
| `profile_pic_url` | text (link) | profile | Profile picture |
| `is_verified` | boolean | profile | Verified badge |
| `external_url` | text (link) | profile | The link in the bio |
| `shortcode` | text | post | Instagram's own post code |
| `url` | text (link) | post | Permanent post link (`/reel/` for reels, `/p/` otherwise) |
| `type` | text | post | `reel`, `image` or `carousel` |
| `posted_at` | date | post | Posting time, UTC. Exact in "Full" detail, day-precision in "Basic" |
| `caption` | text | post | Full caption text |
| `hashtags` | array | post | Every hashtag, lowercased, without the `#` |
| `mentions` | array | post | Every @mention, lowercased, without the `@` |
| `like_count` | number | post | Likes |
| `comment_count` | number | post | Comments |
| `view_count` | number | post | Views. Videos and reels only - an image has none, and the field is empty rather than zero |
| `duration_s` | number | post | Video length in seconds |
| `width` | number | post | Media width in pixels |
| `height` | number | post | Media height in pixels |
| `display_url` | text (link) | post | Full-size still |
| `video_url` | text (link) | post | Direct `.mp4` link. Empty for images |
| `thumbnail_url` | text (link) | post | Thumbnail still |
| `carousel_children` | array | post | One `{type, display_url, video_url}` per slide, for carousels |
| `audio` | object | post | `{title, artist, is_original}` for reels with an attributed track |
| `tagged_users` | array | post | Usernames tagged in the media. **"Full" detail only** |
| `location` | object | post | `{id, name}` when the post has one. **"Full" detail only** |
| `is_paid_partnership` | boolean | post | Always `false` - see the caveat below |
| `coauthors` | array | post | Collaborator usernames. **"Full" detail only** |
| `detailFetched` | boolean | post | `true` if this post's own page was read and parsed. Only these rows are charged the Full-detail surcharge |
| `raw` | object | post | The untouched source objects: `{stub, embed, post}` |
| `scrapedAt` | date | both | When this row was fetched |

Deselect any field in the Input tab's column picker to drop it from every row - `query`, `found`,
`status`, `message` and `scrapedAt` always stay.

### What you can and cannot get without logging in

This actor never logs in, never uses a cookie or a session token, and has no input field that
could accept one. That is a deliberate design choice, and it has hard consequences worth knowing
before you buy:

- **About 12 posts per tab.** Instagram serves a signed-out visitor exactly 12 posts from the
  posts grid and 12 from the reels tab, and refuses every request for the next page. This actor
  reads both tabs and merges them, so a typical account yields somewhere between 12 and 24 unique
  posts. On one test account the two tabs shared 11 of 12 posts and produced 13 unique ones. No
  setting raises this. When your "Most posts to return" is larger than what the account exposes,
  the run's summary record says so explicitly rather than quietly handing you fewer rows.
- **`is_paid_partnership` is always `false`.** The field does not exist anywhere in what Instagram
  serves a logged-out visitor - not merely absent on some posts, absent from the data entirely. It
  is kept as a column so the row shape stays stable for callers that expect it, but it carries no
  information. Do not filter on it.
- **`view_count` is videos only.** Images and carousels of images have no view count at all, so
  the field is empty for them rather than `0`.
- **No comment text.** Comment counts are available; the comments themselves are behind a login.
- **`tagged_users`, `location` and `coauthors` need "Full" detail.** They live only on each post's
  own page, which is the more expensive of the two per-post requests.
- **Private accounts return a miss, not an error.** A private profile comes back with
  `found: false`, `status: NOT_FOUND` and a message saying so.

### Pricing

- **Post returned**: $14 per 1,000 posts
- **Full detail added**: $12 per 1,000 posts

Plus a $0.00005 start fee per run. Each event above is billed independently, only when it actually returns data — misses (`found:false`) are never charged.

Billing is per post, in two parts:

- **`post`** fires once for every post row delivered. Profile rows are never billed - only
  `kind: "post"` rows count toward `postCount`.
- **`post-detail`** is a surcharge that fires only in "Full" detail, and only for posts whose
  second request actually came back with data. A "Basic" run never fires it at all.

The surcharge exists because the two modes genuinely cost different amounts to run: "Full" reads a
second page per post and moves about 1.7x the data. Charging one blended price would make every
"Basic" run subsidise the "Full" ones, so the modes are priced apart instead.

A username that returns no posts (private, nonexistent, or everything filtered out by your date or
media-type settings) costs nothing beyond the flat run fee. A detail request that fails or comes
back empty is not charged either - you still get the row, built from what the cheaper sources gave.

### How to use

1. **In the Apify Console.** Open the actor page and click **Start** — the `usernames` field is already pre-filled with a working example. Results land in the run's dataset as soon as each item is found.
2. **Via the API.** Call it directly with a POST request — no Console needed once you have an API token:
   ```bash
   curl "https://api.apify.com/v2/acts/accountable_eel~ig-profile-scraper/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
     -X POST \
     -H "Content-Type: application/json" \
     -d '{"usernames":["instagram","natgeo"]}'
   ```
3. **On a schedule.** Save this actor as an Apify **Task** with the input you want, then add a **Schedule** (hourly, daily, weekly) so it runs on its own — no server of your own required.

4) Set "How much detail per post" to **Basic** if you only need engagement, media links and the
   audio track - it skips a whole request per post and is not charged the detail surcharge.
5) Set "Only posts since" to a date when you are running the same accounts on a schedule: older
   posts are dropped before any per-post request, so a weekly run pays only for the week.

### Input

```json
{
  "usernames": [
    "instagram",
    "natgeo"
  ]
}
```

One Instagram username per line. @name, a bare name, or a full profile link all work. No login and no cookies: this reads only what a signed-out visitor can see. Accepted formats: instagram, @natgeo, https://www.instagram.com/nasa/.

| Setting | What it does |
|---|---|
| **Most posts to return per profile** | Your budget control, since you pay per post. The real ceiling is about 12 to 24 per profile whatever you set - see the logged-out section above |
| **Only posts since** | A date like `2026-08-01`. Older posts are dropped for free, before any per-post request |
| **Which media to collect** | `All` merges the posts grid and the reels tab. `Reels only` reads the reels tab, which is the only place some reels appear. `Posts only` skips the reels tab and is the cheapest |
| **Include a profile row** | Adds one unbilled row per username with followers, bio, bio link and verified status |
| **How much detail per post** | `Full` adds exact posting time, tagged accounts, location and collaborators, and is charged a per-post surcharge. `Basic` skips both |

### Sample output

| query | found | status | username | postCount | truncated | detailCount | posts | kind | ig\_user\_id | full\_name | bio | followers | following | posts\_count | profile\_pic\_url | is\_verified | external\_url | shortcode | url | type | posted\_at | caption | hashtags | mentions | like\_count | comment\_count | view\_count | duration\_s | width | height | display\_url | video\_url | thumbnail\_url | carousel\_children | audio | tagged\_users | location | is\_paid\_partnership | coauthors | detailFetched | raw | scrapedAt |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| instagram | true | OK | <username> | <posts returned> | <more posts were available> | <posts with full detail fetched> | \<all rows found (full list)> | \<row type (profile or post)> | <instagram user id> | \<display name (profile row)> | \<bio (profile row)> | \<followers (profile row)> | \<following (profile row)> | \<total posts on the account (profile row)> | \<profile picture (profile row)> | \<verified (profile row)> | \<bio link (profile row)> | <post shortcode> | <post link> | <post type> | <posted on> | <caption> | <hashtags> | <mentions> | <likes> | <comments> | \<views (videos only)> | <video length in seconds> | <width> | <height> | <image> | <video file> | <thumbnail> | <carousel slides> | <audio track> | <tagged accounts> | <location> | <paid partnership> | <collaborators> | <full detail fetched for this post> | <raw source objects> | 1970-01-01T00:00:00.000Z |

A username that returns nothing comes back as a row with `"found": false` and is never charged.

### Use it from Clay, n8n, Make, or an AI agent

This actor runs synchronously over plain HTTP — call it directly from a script, a workflow tool, or an AI agent, no Apify Console needed once you have an API token.

```bash
curl "https://api.apify.com/v2/acts/accountable_eel~ig-profile-scraper/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"usernames":["instagram","natgeo"]}'
```

**n8n.** Add an HTTP Request node: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~ig-profile-scraper/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body Content Type `JSON`, JSON Body `{"usernames":["instagram","natgeo"]}` (swap in an expression from an earlier node for a real value).

**Clay.** Add an "HTTP API" column: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~ig-profile-scraper/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body `{"usernames":["{{profile}}"]}`, mapping the row's profile into the `usernames` array.

**MCP.** In Claude, Cursor, or any MCP client with the Apify MCP server, ask for "Instagram Scraper API: Profile Posts & Reels" — the agent will find and run this actor.

The run also writes a summary to its `OUTPUT` key-value record: `{requested, returned, errors[],
caps[]}`. `errors` lists every username that missed and why; `caps` names any profile where
Instagram's logged-out limit stopped the run short of your requested count. A workflow can read
that one record instead of scanning the whole dataset to find out whether the run did what you
asked.

### Tips

- Run competitor accounts as one input list rather than one run each - the profile rows are free,
  and one run gives you a single table to pivot on.
- Use `Posts only` when you are auditing captions and hashtags: reels carry the same caption data
  and skipping the reels tab removes a whole page load per username.
- Use `Reels only` when you are studying short-form performance. The reels tab is the only place
  Instagram exposes reels that have been hidden from the main grid, and it carries play counts
  inline.
- Keep `raw` selected while you are building a workflow and deselect it once your mapping is
  settled - it is by far the largest field in a row.
- Leave the proxy on Residential. Instagram rate-limits Apify's shared datacenter IPs on this
  route, and a `REQUEST_FAILED` or `BLOCKED` row is usually that. Lowering "Max concurrency" in
  Advanced helps too.
- Do not build a report that needs more than about two dozen posts per account. That is not a
  limit of this actor, it is Instagram's, and no scraper without a logged-in session gets past it.

### vs. alternatives

| | What it costs | What you get | Trade-off |
|---|---|---|---|
| **This actor** (`ig-profile-scraper`) | Per post returned, plus a surcharge per post in Full detail; nothing for a miss or for the profile row | One frozen, flat row per post or reel - caption, hashtags, mentions, likes, comments, views, duration, dimensions, media links, audio, tagged accounts, location - plus the raw source objects and an optional free profile row | About 12 to 24 posts per profile, because it never logs in. `is_paid_partnership` is always `false` and comment text is unavailable. It lists media, it does not download it |
| **apify/instagram-scraper** | About $2.70 per 1,000 posts | The Store leader by a wide margin: 4.73 stars over 590 reviews, far broader input surface (hashtags, locations, search), and deeper history | If you need hashtag or location scraping, or more posts per account than a logged-out view allows, use it. This one is narrower on purpose: profiles only, a fixed row shape aimed at agents and tables, and the raw payload passed through |
| **apify/instagram-reel-scraper** | About $2.60 per 1,000 reels | Reels specifically, from the same publisher | Reels only. This actor covers reels as one of three media types in the same row shape, so a mixed account does not need two runs and two schemas |
| **Instagram's own Graph API** | Free, with a Meta app | Official, complete, and the only way to get insights on accounts you control | It only reaches accounts you own or have been granted access to, and requires app review and a Business or Creator account. This reads public profiles you do not control, which the Graph API cannot do |
| Doing it yourself | Your time, plus the maintenance | The same data | The header set that makes these pages serve data at all, the double-encoded embed payload, the two tabs that each carry half the fields, and the fact that the pagination endpoint returns a null user logged out. That research is what this actor absorbs |

Prices for third-party tools are their published list prices as of September 2026 and are not
tracked here - check the vendor before relying on the comparison.

### Data & privacy

This actor reads public Instagram profiles that anyone can see without logging in. It does not log
in, does not use cookies or session tokens, does not solve CAPTCHAs, and does not reveal hidden or
private content. Private accounts return a miss.

The rows include usernames, display names, biographies and profile pictures, which are personal
data about real people. If you process them you are the controller of that processing and need
your own lawful basis for it - this actor gives you the data, not permission to use it. Do not use
it to build profiles of private individuals. Not affiliated with Instagram or Meta.

**Is this allowed?** It collects the same public post data your browser shows a signed-out
visitor, for the accounts you choose, at a modest request rate. It is built for analysis and
monitoring of public accounts, not for copying Instagram. Check that your use fits Instagram's
terms and your local law.

### FAQ

**Why do I only get about 12 posts per account?**
Because nobody logged in. Instagram serves a signed-out visitor 12 posts from the profile grid and
12 from the reels tab, and answers every request for a further page with an empty user. This actor
reads both tabs and merges them, which is the maximum available without an account. Raising "Most
posts to return" will not change it; the run's `OUTPUT` summary tells you when this happened.

**Why is `is_paid_partnership` always false?**
The field is not present in anything Instagram serves logged out. It is kept as a column so the
row shape never changes for callers that expect it, but it carries no information and should not
be filtered on.

**Why is `view_count` empty on some rows?**
Those rows are images or image carousels, which have no view count. Videos and reels have one.
Empty is deliberate - reporting `0` would look like a real measurement.

**What is the difference between Basic and Full detail?**
Basic reads one page per post (the embed) and gives caption, hashtags, mentions, likes, comments,
views, video length, dimensions, the media links and the audio track, with the posting date to the
day. Full reads a second page per post and adds the exact posting time, tagged accounts, location
and collaborators, at about 1.7x the data transfer and a per-post surcharge. The `detailFetched`
column tells you, row by row, which posts actually got the second request.

**Does it download the videos and images?**
No. You get direct links (`video_url`, `display_url`, `thumbnail_url`, and per-slide links for
carousels) and fetch what you actually want yourself. Listing and downloading are kept as separate
jobs so you are never billed for bytes you did not ask for.

**Can I get comments?**
Comment counts, yes. Comment text, no - it is behind a login, and this actor does not log in.

**Do I need a proxy?**
Residential is the default and you should leave it on. Instagram served these pages happily to a
plain request from an ordinary connection during the build probe, but it answered Apify's shared
datacenter IPs with HTTP 429 on the first live run: one username was rescued by session rotation
on its third attempt and the other exhausted its retries. The same call through Residential
returned every row with no errors. That is why the price is set against residential data transfer
rather than the cheaper datacenter rate.

**Can an AI agent call this directly?**
Yes. It is registered on the Apify MCP server, and the field names are frozen specifically so an
agent's mapping does not break between runs. The `OUTPUT` summary record gives an agent a single
place to check whether the run was complete.

### Related actors

- [TikTok Profile Lookup](https://apify.com/accountable_eel/tiktok-profile-lookup) - follower,
  video and engagement stats for a TikTok handle, same no-login approach.
- [Threads Profile Lookup](https://apify.com/accountable_eel/threads-profile-lookup) - the
  equivalent profile lookup for Meta's Threads.
- [Pinterest Search Scraper](https://apify.com/accountable_eel/pinterest-search-lookup) - one row
  per pin for a keyword search, billed per row the same way.

# Actor input Schema

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

One Instagram username per line. @name, a bare name, or a full profile link all work. No login and no cookies: this reads only what a signed-out visitor can see. Accepted formats: instagram, @natgeo, https://www.instagram.com/nasa/. You're only charged for the ones we actually find — a miss costs nothing.

## `testRun` (type: `boolean`):

Turn this on to test your input on a small sample before running the full list. Turn it off to process everything.

## `onlyFound` (type: `boolean`):

Only keep rows where something was actually found. Misses are always free, whether or not you show them here.

## `includeKeywords` (type: `array`):

Optional. Only keep results that mention at least one of these words (e.g. a job title, a city, a product name). Leave empty to keep everything.

## `excludeKeywords` (type: `array`):

Optional. Drop any result that mentions one of these words. Leave empty to skip nothing.

## `maxResults` (type: `integer`):

Optional. Stop the run once this many results have been found — useful for a quick, cheap sample. Leave blank for no limit.

## `limit` (type: `integer`):

You pay per post returned, so this is also your budget control. Instagram shows a signed-out visitor at most 12 posts per tab, so about 12 to 24 per profile is the real ceiling no matter what you set here.

## `since` (type: `string`):

Optional. A date like 2026-08-01. Posts older than this are dropped before any detail request is made, so they cost you nothing. Leave empty for everything the profile shows.

## `mediaType` (type: `string`):

"All" reads both the posts grid and the reels tab and merges them. "Reels only" reads the reels tab, which is also the only place Instagram shows reels that are hidden from the main grid. "Posts only" skips the reels tab entirely and is the cheapest option.

## `includeProfile` (type: `boolean`):

On by default. Adds one row per username with followers, following, bio, bio link, profile picture and verified status. Profile rows are never billed.

## `detail` (type: `string`):

"Full" adds the exact posting time, tagged accounts, location and collaborators by reading each post's own page: about 4x the data transfer. "Basic" reads only the embed page, which is 2.3x cheaper and still gives likes, comments, views, video length, dimensions, the video file and the audio track, but dates the post to the day rather than the minute.

## `columns` (type: `array`):

Choose which pieces of information to include in each result row. All are included by default.

## `expandRows` (type: `boolean`):

When on, each post found gets its own row instead of being grouped under its profile. You're still only charged once per profile, no matter how many rows it produces.

## `maxConcurrency` (type: `integer`):

Parallel requests. Keep conservative — this target has no browser fallback, so getting blocked costs more than slow-and-steady.

## `proxyConfiguration` (type: `object`):

Apify Proxy config. Residential recommended for anti-bot-sensitive targets.

## Actor input object example

```json
{
  "usernames": [
    "instagram",
    "natgeo"
  ],
  "testRun": false,
  "onlyFound": false,
  "includeKeywords": [],
  "excludeKeywords": [],
  "limit": 30,
  "since": "",
  "mediaType": "all",
  "includeProfile": true,
  "detail": "full",
  "columns": [
    "username",
    "postCount",
    "truncated",
    "detailCount",
    "posts",
    "kind",
    "ig_user_id",
    "full_name",
    "bio",
    "followers",
    "following",
    "posts_count",
    "profile_pic_url",
    "is_verified",
    "external_url",
    "shortcode",
    "url",
    "type",
    "posted_at",
    "caption",
    "hashtags",
    "mentions",
    "like_count",
    "comment_count",
    "view_count",
    "duration_s",
    "width",
    "height",
    "display_url",
    "video_url",
    "thumbnail_url",
    "carousel_children",
    "audio",
    "tagged_users",
    "location",
    "is_paid_partnership",
    "coauthors",
    "detailFetched",
    "raw"
  ],
  "expandRows": true,
  "maxConcurrency": 2,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

No description

# 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": [
        "instagram",
        "natgeo"
    ],
    "includeKeywords": [],
    "excludeKeywords": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("accountable_eel/ig-profile-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": [
        "instagram",
        "natgeo",
    ],
    "includeKeywords": [],
    "excludeKeywords": [],
}

# Run the Actor and wait for it to finish
run = client.actor("accountable_eel/ig-profile-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": [
    "instagram",
    "natgeo"
  ],
  "includeKeywords": [],
  "excludeKeywords": []
}' |
apify call accountable_eel/ig-profile-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,accountable_eel/ig-profile-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/nL3WVcrHtNXRpTywE/builds/wBEOCB1sbhi37SGp3/openapi.json
