# Instagram API Scraper (`scraper-engine/instagram-api-scraper`) Actor

⚡ Lightning-fast Instagram scraper. Extract posts, reels, comments, profile details, hashtag posts, and search results from any public Instagram URL or username — no login, no cookies, no headless browser.

- **URL**: https://apify.com/scraper-engine/instagram-api-scraper.md
- **Developed by:** [Scraper Engine](https://apify.com/scraper-engine) (community)
- **Categories:** Automation, Lead generation, Social media
- **Stats:** 124 total users, 31 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.99 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#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 API Scraper — Posts, Reels, Likes and Captions

Instagram API Scraper extracts public Instagram profile feeds through Instagram's own JSON API — no login, no cookies, no headless browser. Each post or reel comes back as a typed row: caption, hashtags, mentions, like count, comment count, timestamp, real carousel children and every image size variant Instagram sent. Paste usernames or profile URLs, pick posts, reels, mentions or profile details, and rows stream into the dataset as each page lands.

⚠️ **Read this before your first run.** Instagram walls large parts of its API behind a session cookie. Profile **posts**, **reels**, **mentions** and partial **profile details** work logged-out. **Comments**, **stories**, single-post and single-reel URLs, hashtag feeds, location feeds and search do **not** — those targets return an **uncharged accounting row** carrying a typed `errorReason` instead of a charged empty row. Nothing is silently substituted, and you are not billed for a wall you cannot pass.

### What is Instagram API Scraper?

Instagram API Scraper is an Apify Actor that calls Instagram's public web API endpoints directly and normalizes the responses into stable JSON rows. One run covers one mode: a profile's post feed, a profile's reels feed, `@mentions` mined from captions, or profile identity fields.

No Instagram account, login, cookie or session is used. Every value it returns is already served to an anonymous visitor by Instagram's own endpoints.

It is built for social-media analysts tracking creator output, growth and agency teams auditing competitor feeds, and developers piping public post data into a dashboard, warehouse or AI agent.

### What Instagram data is publicly available to scrape?

Instagram serves a public profile's whole media feed to signed-out clients — captions, media, like counts and comment counts included. What it does not serve without a session cookie is conversation, ephemeral content, discovery surfaces, and the profile-info aggregate.

| Data Category | Signed-out visitor | Requires an Instagram login |
| ----- | ----- | ----- |
| A public profile's post feed — captions, media, likes, comment counts | ✅ Public | — |
| A public profile's reels feed | ✅ Public | — |
| Owner username, full name, verification badge, profile picture | ✅ Public | — |
| Hashtags and `@mentions` written inside captions | ✅ Public | — |
| Carousel children and every image size variant | ✅ Public | — |
| Comments and replies on a post or reel | ❌ | Login — the media-info endpoint redirects to `/accounts/login/` |
| Stories and highlights | ❌ | Login — the story tray endpoints redirect to `/accounts/login/` |
| Single post / reel detail by shortcode URL | ❌ | Login |
| Hashtag feeds, location feeds and Instagram search | ❌ | Login |
| Follower, following and post counts, bio, external URL, category | ❌ | No keyless endpoint remains — see below |
| Private accounts, follower lists, direct messages | ❌ | Login plus an approved follow |

Instagram API Scraper only returns publicly visible data — what any signed-out visitor sees. Nothing behind a login wall.

⚠️ **Follower counts and bios are not available to this Actor.** The Actor's build-time probe (2026-07-19) found Instagram's profile-info surface `/api/v1/users/web_profile_info/` answering HTTP 429 from every network exit, and HTTP 400 even when replayed inside a live logged-in browser. `resultsType: "details"` therefore returns `followersCount`, `followsCount`, `postsCount`, `biography`, `externalUrl`, `category` and `isBusinessAccount` as `null`, lists them in `unavailableFields`, and explains why in `unavailableReason` — rather than shipping zeros and empty strings that read as real data.

### What data can I extract with Instagram API Scraper?

The Actor writes five row shapes into one dataset. Every row carries a `section` field naming its shape, and the Actor ships a dataset view per shape so you can read, sort or export them separately.

| `section` | Row shape | Charged |
| ----- | ----- | ----- |
| `posts` | One media row per post — photo, video or carousel | ✅ `row_result` |
| `reels` | One media row per reel, same shape, `type` forced to `Video` | ✅ `row_result` |
| `profiles` | One profile identity row per target (`resultsType: "details"`) | ✅ `row_result` |
| `mentions` | One row per `@mention` found in a caption | ✅ `row_result` |
| `accounting` | One diagnostic row per blocked, walled, private or empty target | ❌ Uncharged |
| `search` | Search result rows — structurally supported, login-walled in practice | ✅ `row_result` |

#### Post and reel media fields

Posts and reels share one schema, so a feed run and a reels run join on the same keys.

| Field Name | Description |
| ----- | ----- |
| `id` | Instagram's numeric media id, as a string |
| `type` | `Photo`, `Video` or `Sidecar` (carousel). Reels rows are always `Video` |
| `productType` | Instagram's own product label for the media, e.g. `clips`, `feed`, `carousel_container` |
| `shortCode` | Media shortcode — the segment in `instagram.com/p/<shortCode>/` |
| `url` | Canonical post URL built from `shortCode` |
| `caption` | Full caption text as written by the account owner |
| `hashtags` | Array of hashtag words parsed out of the caption, without the `#` |
| `mentions` | Array of usernames parsed out of the caption, without the `@` |
| `likesCount` | Like count, or `null` when the owner has hidden counts |
| `likeCountsHidden` | `true` when Instagram flagged like and view counts as hidden by the owner |
| `commentsCount` | Comment count as reported on the media, or `null` |
| `isCommentsDisabled` | `true`/`false` when Instagram sends a usable signal, `null` when it sends none |
| `firstComment` | Text of the first preview comment, or `null` when no comment reaches a logged-out client |
| `latestComments` | Array of preview comment objects, or `null` — see the nested shapes below |
| `timestamp` | UTC ISO timestamp the media was posted |
| `captionCreatedAt` | UTC ISO timestamp the caption itself was written, or `null` |
| `captionIsEdited` | `true`/`false` when Instagram carries the flag, `null` when it does not |
| `displayUrl` | Highest-priority image URL Instagram returned for the media |
| `images` | Flat array of image URLs — the media's own, plus one per carousel child |
| `allImageVariants` | Every image size variant in the payload, each with `url`, `width`, `height` |
| `imageVariantCount` | Number of entries in `allImageVariants` |
| `dimensionsHeight` | Original media height in pixels, or `null` |
| `dimensionsWidth` | Original media width in pixels, or `null` |
| `alt` | Instagram's accessibility caption for the media, or `null` |
| `childPosts` | Array of carousel child objects — empty array for single-media posts |
| `childPostsCount` | Number of entries in `childPosts` |
| `ownerUsername` | Account handle that posted the media |
| `ownerFullName` | Display name on the posting account |
| `ownerId` | Numeric account id, as a string |
| `ownerIsVerified` | `true`/`false` when the payload carries the flag, `null` when it does not |
| `fbCrossPostId` | Facebook cross-post id (`fbid`) when Instagram sends one, otherwise `null` |
| `inputUrl` | The exact `directUrls` entry this row came from |
| `scrapedAt` | UTC ISO timestamp the row was built |
| `section` | Row shape tag — `posts` or `reels` |
| `parentData` | Source profile object. **Present only when `addParentData` is `true`** — omitted, not nulled |

#### Media state and honesty rules

Three of these fields exist specifically so that a missing value never reads as a real one.

`likesCount` is `null` — never `0` — when `likeCountsHidden` is `true`, because Instagram sends no trustworthy count in that state. `isCommentsDisabled` is `null` when Instagram sends no usable signal; the Actor reads `comments_disabled`, then `is_social_ufi_disabled`, then inverts `comment_likes_enabled`, and stops there rather than manufacturing a `false`. `firstComment` and `latestComments` are `null` rather than `""` and `[]`, because an empty array would read as "checked, and there are none" when the truth is "not reachable logged-out".

`allImageVariants` is the field that costs nothing extra. Instagram already ships every size variant of a media in the same response — typically 10 to 14 of them — and most scrapers keep only the first. This Actor keeps all of them with real pixel dimensions, so you can pick a thumbnail or a full-resolution frame without a second request.

#### Nested objects and arrays

`childPosts[]` — one object per carousel child, with `id`, `type` (`Image` or `Video`), `shortCode`, `url`, `dimensionsHeight`, `dimensionsWidth`, `displayUrl`, `allImageVariants`, `images`, `alt`, `ownerId`, `childPosts` (always an empty array), and `caption`, `hashtags`, `mentions`, `likesCount`, `timestamp`, `commentsCount`, `firstComment`, `latestComments` set to `null`. Those last eight are genuinely not exposed per child, so they are declared unavailable rather than faked as `0` and `""`. Instagram sends no shortcode for some children, in which case `shortCode` and `url` are `null` rather than a link pointing at `instagram.com/p//`.

`allImageVariants[]` — `url`, `width`, `height` per variant.

`latestComments[]` — `id`, `text`, `ownerUsername`, `ownerProfilePicUrl`, `timestamp`, `repliesCount`, `replies`, `likesCount`, and an `owner` object. The nested `owner` object keeps Instagram's original snake\_case naming — `username`, `full_name`, `profile_pic_url`, `profile_pic_id`, `is_verified`, `is_private`, `is_mentionable`, `latest_reel_media`, `id` — while its parent object is camelCase. That inconsistency is upstream and is preserved so your mapping matches what actually arrives. On the primary feed surface this array is `null`; it only fills when the Actor falls back to Instagram's Relay timeline query and that response happens to carry preview comments.

`parentData` — `name`, `type`, `url` for the source profile. Added to post and reel rows only, and only when `addParentData` is `true`. It does not attach to profile-detail rows or mention rows.

#### Profile detail fields

Returned by `resultsType: "details"`, one row per profile target.

| Field Name | Description |
| ----- | ----- |
| `id` | Numeric account id, as a string |
| `username` | Account handle |
| `fullName` | Display name, or `null` |
| `isVerified` | Verification flag, or `null` when the payload omits it |
| `isPrivate` | Private-account flag, or `null` when the payload omits it |
| `profilePicUrl` | Standard-resolution avatar URL |
| `profilePicUrlHD` | Highest-resolution avatar URL available in the payload |
| `profileUrl` | Canonical profile URL |
| `detailsSource` | Which surface the row was built from — `post_feed_user_object` |
| `unavailableFields` | Array naming every field with no keyless source on this run |
| `unavailableReason` | Plain-language explanation of why those fields are `null` |
| `biography` | Always `null` — no keyless source |
| `externalUrl` | Always `null` — no keyless source |
| `followersCount` | Always `null` — no keyless source |
| `followsCount` | Always `null` — no keyless source |
| `postsCount` | Always `null` — no keyless source |
| `isBusinessAccount` | Always `null` — no keyless source |
| `category` | Always `null` — no keyless source |
| `inputUrl` | The `directUrls` entry this row came from |
| `scrapedAt` | UTC ISO timestamp the row was built |
| `section` | Row shape tag — `profiles` |

#### Mention fields

Returned by `resultsType: "mentions"`: `type` (always `mention`), `mentionedUsername`, `postShortCode`, `postUrl`, `ownerUsername`, `timestamp`, `inputUrl`, `scrapedAt`, `section`. Mentions are mined from the captions of the target profile's own posts — this is "who this account tags", not "who tags this account".

#### Uncharged accounting fields

Every accounting row carries `type` (always `accounting`), `target`, `targetKind`, `resultsType`, `errorReason`, `message`, `itemsReturned`, `inputUrl`, `scrapedAt` and `section` (always `accounting`). These rows are pushed **without a charged event name**, so a wall, a block, a private account or a typo shows up in your dataset without being billed as a `row_result`.

`errorReason` is a fixed vocabulary of eleven values:

| `errorReason` | What happened |
| ----- | ----- |
| `COMMENTS_LOGIN_WALLED` | No comment data reaches a logged-out client on any surface |
| `STORIES_LOGIN_WALLED` | Stories need an authenticated session |
| `POST_DETAIL_LOGIN_WALLED` | A single post or reel URL was given; the media-info surface is walled |
| `HASHTAG_LOGIN_WALLED` | A hashtag URL was given; the tag feed is walled |
| `LOCATION_LOGIN_WALLED` | A location URL was given; the location feed is walled |
| `SEARCH_LOGIN_WALLED` | `search` was set; Instagram's search endpoint redirects to the login page |
| `EMPTY_FEED_PRIVATE_OR_RESTRICTED` | HTTP 200 with zero items — private, restricted, age-gated or no public posts |
| `PROFILE_ID_UNRESOLVED` | No numeric user id could be resolved from any live surface |
| `UNSUPPORTED_TARGET_FOR_MODE` | The target kind does not fit the selected `resultsType`, or could not be parsed |
| `FETCH_FAILED` | Every live surface failed for this target after retries |
| `NO_MENTIONS_IN_CAPTIONS` | Captions were scanned successfully and contained no `@mention` |

To read only billed data rows, filter on the absence of `errorReason`:

```python
clean = [r for r in rows if r.get("errorReason") is None]
```

#### Search fields

If Instagram reopens `/api/v1/web/search/topsearch/` to signed-out clients, the Actor already shapes the response: `user` rows carry `type`, `id`, `username`, `full_name`, `is_verified`, `is_private`, `profile_pic_url`, `follower_count`; `hashtag` rows carry `type`, `id`, `name`, `media_count`, `search_result_subtitle`, `url`; `place` rows carry `type`, `id`, `name`, `subtitle`, `lat`, `lng`, `url`. Every search row also gets `scrapedAt` and `section`. The original snake\_case keys are kept for backward compatibility and camelCase aliases are added alongside them — `fullName`, `isVerified`, `isPrivate`, `profilePicUrl`, `followerCount`, `mediaCount`, `searchResultSubtitle` — so the dataset contract stays consistent with the other sections. In practice this path returns `SEARCH_LOGIN_WALLED` today.

#### 🤖 Add-on: Need additional Instagram data?

Four sibling Actors extend this base engine rather than duplicating it. **Instagram API Scraper With Reel Views & Audio Tracks** adds reel view counts, video URLs and audio-track attribution. **Instagram API Scraper By Likes, Keywords & Date Range** applies engagement, keyword and date filters during the run instead of after it. **Instagram API Scraper & Brand Collaboration Partners** adds tagged accounts, co-authors and sponsorship partners. **Instagram API Scraper: Engagement Rate Per Follower** adds follower-denominated engagement rate.

### How does Instagram API Scraper differ from the official Instagram Graph API?

Meta's Instagram Graph API is public and documented, but its scope is accounts you control: it reads Instagram Business and Creator accounts that you or your app's users have authorized through OAuth, after Meta app review. It is not a route to arbitrary third-party public profiles. Instagram API Scraper reads the public web endpoints instead, which is why it accepts any public username as input — and also why the walled surfaces listed above stay walled.

| Feature | Instagram Graph API | Instagram API Scraper |
| ----- | ----- | ----- |
| Reads a third-party public profile you do not own | ❌ Authorized accounts only | ✅ Any public profile |
| Meta app review and OAuth flow | ✅ Required | ❌ Not used |
| Instagram Business or Creator account required | ✅ Required | ❌ Not required |
| Comments, stories and insights | ✅ For authorized accounts | ❌ Login-walled — uncharged accounting row |
| Follower, following and post counts | ✅ For authorized accounts | ❌ `null`, declared in `unavailableFields` |
| Output shape | Meta's documented Graph API schema | One normalized row per media item, stable across runs |
| Request budget | Published per-app limits — check Meta's docs | 12 items per request, 200 pages, 2400 items per target |

Check Meta's current published Instagram Graph API documentation for the exact permissions, review requirements and rate limits before choosing between them — those terms change and are not reproduced here.

Use the Graph API when you own or manage the accounts, need comments and insights, and want a supported contract. Use Instagram API Scraper for competitor and creator research across accounts you have no authorization over.

### How to use Instagram API Scraper

Instagram API Scraper runs on Apify. Start it from the Apify Console or call it through the Apify API — the only credential involved is your Apify token.

1. Open Instagram API Scraper on Apify and click **Try for free**
2. Paste one or more public profile URLs or bare usernames into **Instagram URLs or usernames** (`directUrls`) — this is the only required input
3. Pick **What to scrape** (`resultsType`): `posts`, `reels`, `mentions` or `details`. Leave it on `posts` for a profile feed
4. Set **Max results per URL** (`resultsLimit`) — 1 to 2400 per target
5. Optionally set **Only items newer than** (`onlyPostsNewerThan`) to an absolute date or a relative window
6. Leave **Proxy configuration** off unless you want to force a tier — the Actor runs direct and escalates by itself
7. Click **Start**, then export the dataset as JSON, CSV, Excel or XML, or read it through the Apify API

Match the target to the mode. Profile URLs and bare usernames work for all four working modes. Single post URLs, reel URLs, hashtag URLs and location URLs are login-walled by Instagram — give the owner's profile instead, and the profile feed returns the post.

#### How to scale to bulk profile extraction

`directUrls` is a list, so one run can cover as many profiles as you need. Both plain strings and `{ "url": "..." }` objects are accepted, so a list produced by another tool usually feeds in unchanged, and `@handle`, `handle` and a full profile URL all resolve to the same target.

`resultsLimit` is **per target**, not a run total: ten profiles at `resultsLimit: 100` is up to 1000 rows. The engine only opens as many pages as your limit needs — `ceil(limit / 12) + 2`, capped at 200 pages — so a small limit does not pay for deep pagination. Targets are processed one after another with a short randomised pause between them. For recurring collection, put the same input on an Apify schedule and diff `likesCount` and `commentsCount` per `shortCode` between runs.

### What can you do with Instagram post and reel data?

- 📈 **A social media analyst** benchmarking a competitor pulls 200 posts per account and ranks them by `likesCount` against `commentsCount` to separate reach from conversation, using `productType` to compare reels against static feed posts.
- 🏷️ **A brand strategist** auditing partnerships runs `resultsType: "mentions"` across a creator list and counts `mentionedUsername` per `ownerUsername` to see which brands each creator actually tags, and how often.
- 🖼️ **A content ops team** building an asset library reads `allImageVariants` and `childPosts` to pull the exact resolution they need per carousel frame, without a second request per image.
- 🔍 **A growth marketer** tracking posting cadence re-runs the same profiles weekly with `onlyPostsNewerThan: "7 days"` and groups `timestamp` by weekday to find when a competitor actually publishes.
- 🤖 **An AI engineer** building a social-listening agent indexes `caption`, `hashtags`, `mentions` and `ownerUsername` into a vector store, so the agent can answer "what themes has this account posted about this month" against live feed data rather than a stale export.
- 🧾 **A data engineer** running the same job nightly filters on `errorReason` to build an exception queue — private accounts, walled targets and failed fetches all arrive as typed, uncharged rows instead of silent gaps.

Every one of these is callable from an agent framework over the Apify API, since the Actor is a standard HTTP-triggered run.

### How does Instagram API Scraper handle rate limits and blocking?

There is no headless browser and no CAPTCHA solver in this Actor — it does not solve challenges, and it does not claim to. What it does is keep a plain HTTP session credible and escalate its network path only when Instagram pushes back.

Every run starts on a **direct connection with no proxy at all**, because Instagram's live endpoints answer the Apify container directly. Requests carry a rotating Linux Chrome or Firefox user agent — matched to the container's actual operating system rather than advertising a Windows client from a Linux host — plus Instagram's own web headers. The public app id and CSRF token are harvested at runtime from a real page load, with the cookie jar read before the HTML. That token matters: the reels endpoint is a POST and answers HTTP 403 without it.

When Instagram returns 401, 403, 407, 429, 451 or 503, the Actor climbs a fixed ladder: direct → Apify datacenter → Apify residential, up to three retries on the residential rung, then sticky residential for the rest of the run. Residential endpoints are pinned to sticky sessions because Instagram binds its cookies to the exit IP that issued them, and the session is rebuilt behind the new exit after an escalation for the same reason. On 429 and 503 the Actor also backs off with jitter before retrying. Pages within a target are spaced by a randomised delay, and targets by another.

If a target still cannot be fetched after three failed pages, the Actor writes an uncharged accounting row with `errorReason: "FETCH_FAILED"` and moves on. The run continues; nothing is fabricated and nothing is billed for the failure. The final log line names the proxy tier the run ended on.

### ⬇️ Input

Nine parameters, read straight from the Actor's input schema. Only `directUrls` is required.

| Parameter | Required | Type | Description | Example Value |
| ----- | ----- | ----- | ----- | ----- |
| `directUrls` | Yes | array | Instagram profile URLs or bare usernames, one per line. Plain strings and `{ "url": "..." }` objects are both accepted. Single post, reel, hashtag and location URLs are accepted as input but are login-walled by Instagram and return an uncharged accounting row. | `["https://www.instagram.com/humansofny/", "natgeo"]` |
| `resultsType` | No | string | What to return per target. One of `posts`, `comments`, `details`, `mentions`, `reels`, `stories`. Default `"posts"`. `comments` and `stories` are login-walled and always return an uncharged accounting row. | `"posts"` |
| `resultsLimit` | No | integer | Maximum items per target. Minimum 1, maximum 2400 — the engine's real ceiling of 200 pages at 12 items per page. Default `10`. | `100` |
| `onlyPostsNewerThan` | No | string | Only return items posted on or after this cutoff. Absolute `YYYY-MM-DD`, or a relative window such as `7 days`, `2 months`, `3 years`. Empty means no filter. All times UTC. | `"30 days"` |
| `addParentData` | No | boolean | Attach a `parentData` object with the source profile to each post or reel row. Default `false`. | `false` |
| `search` | No | string | Search query. Instagram's search endpoint redirects logged-out clients to the login page, so this returns an uncharged accounting row rather than results. Kept for input compatibility. Default `""`. | `""` |
| `searchType` | No | string | What the search query targets. One of `user`, `hashtag`, `place`. Default `"hashtag"`. Only read when `search` is set. | `"hashtag"` |
| `searchLimit` | No | integer | Number of search results to return. Minimum 1, maximum 250. Default `1`. Only read when `search` is set. | `1` |
| `proxyConfiguration` | No | object | Apify Proxy settings. Default `{"useApifyProxy": false}` — the Actor runs direct and escalates only when Instagram pushes back. Set a group to force a starting tier; a country code localises the residential rung. | `{"useApifyProxy": false}` |

**Parameters that behave differently from what their names suggest — stated plainly:**

- `search`, `searchType` and `searchLimit` cannot return results today. Instagram's `topsearch` endpoint redirects signed-out clients to the login page. Setting `search` produces one uncharged accounting row with `errorReason: "SEARCH_LOGIN_WALLED"`. The three inputs exist for compatibility with other Instagram Actors' input shape.
- `resultsType: "comments"` and `resultsType: "stories"` are valid enum values that always produce an uncharged accounting row, never data. They stay in the enum so a job configured for another Actor does not fail validation — and so the run tells you why it returned nothing instead of finishing green and empty.
- `resultsLimit` has two defaults. The schema default is `10`, which Apify Console fills in for you. If the key is absent entirely — for example an API call that omits it — the engine falls back to `30`.
- `onlyPostsNewerThan` is validated by a schema pattern that accepts only `YYYY-MM-DD` and `N day|week|month|year(s)`. The engine's parser also understands hours and full ISO timestamps, but the schema regex rejects them before the run starts. Relative months are treated as 30 days and years as 365 days.
- `onlyPostsNewerThan` stops paginating at the first item older than the cutoff. Instagram feeds are reverse-chronological, so this is normally what you want — but a pinned post that predates your cutoff sits at the top of the feed and will end that target early. Leave the filter empty if a profile pins old content.
- `addParentData` attaches `parentData` to post and reel rows only. Profile-detail rows and mention rows are unaffected.

#### Example input

```json
{
  "directUrls": [
    "https://www.instagram.com/natgeo/",
    "https://www.instagram.com/humansofny/",
    "nasa"
  ],
  "resultsType": "posts",
  "resultsLimit": 100,
  "onlyPostsNewerThan": "30 days",
  "addParentData": true,
  "search": "",
  "searchType": "hashtag",
  "searchLimit": 1,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

### ⬆️ Output

Typed, normalized JSON with a consistent shape per `section` across runs. Rows are pushed live as each page finishes, so the dataset fills while the run is still going. Export as JSON, CSV, Excel or XML from the Storage tab, or read the dataset through the Apify API. Seven dataset views ship with the Actor — Posts and reels, Posts overview, Reels, Profile details, Mentions, Run accounting and Search — so each row shape gets a readable table with no transformation on your side.

Absent values are `null`, not fabricated zeros or empty strings. The one field omitted rather than nulled is `parentData`, which appears only when `addParentData` is `true`.

#### Example output

A carousel post row from a `posts` run with `addParentData: true`:

```json
{
  "id": "3641182904472519038",
  "type": "Sidecar",
  "shortCode": "DK7pQzvNXa1",
  "caption": "Fifteen years after the eruption, the forest is coming back. Photographs by @carstenpeter. #volcano #reforestation",
  "hashtags": ["volcano", "reforestation"],
  "mentions": ["carstenpeter"],
  "url": "https://www.instagram.com/p/DK7pQzvNXa1/",
  "commentsCount": 412,
  "firstComment": null,
  "latestComments": null,
  "dimensionsHeight": 1350,
  "dimensionsWidth": 1080,
  "displayUrl": "https://scontent.cdninstagram.com/v/t51.2885-15/512094871_1080x1350_n.jpg",
  "images": [
    "https://scontent.cdninstagram.com/v/t51.2885-15/512094871_1080x1350_n.jpg",
    "https://scontent.cdninstagram.com/v/t51.2885-15/512094872_1080x1350_n.jpg"
  ],
  "alt": "Photo by National Geographic on July 18, 2026.",
  "likesCount": 128744,
  "timestamp": "2026-07-18T14:02:11.000Z",
  "childPosts": [
    {
      "id": "3641182899812340011",
      "type": "Image",
      "caption": null,
      "hashtags": null,
      "mentions": null,
      "url": "https://www.instagram.com/p/DK7pQzvNXa2/",
      "commentsCount": null,
      "firstComment": null,
      "latestComments": null,
      "dimensionsHeight": 1350,
      "dimensionsWidth": 1080,
      "displayUrl": "https://scontent.cdninstagram.com/v/t51.2885-15/512094872_1080x1350_n.jpg",
      "allImageVariants": [
        { "url": "https://scontent.cdninstagram.com/v/t51.2885-15/512094872_1080x1350_n.jpg", "width": 1080, "height": 1350 },
        { "url": "https://scontent.cdninstagram.com/v/t51.2885-15/512094872_750x938_n.jpg", "width": 750, "height": 938 }
      ],
      "images": ["https://scontent.cdninstagram.com/v/t51.2885-15/512094872_1080x1350_n.jpg"],
      "alt": null,
      "likesCount": null,
      "timestamp": null,
      "childPosts": [],
      "ownerId": "787132",
      "shortCode": "DK7pQzvNXa2"
    }
  ],
  "ownerFullName": "National Geographic",
  "ownerUsername": "natgeo",
  "ownerId": "787132",
  "isCommentsDisabled": false,
  "inputUrl": "https://www.instagram.com/natgeo/",
  "productType": "carousel_container",
  "ownerIsVerified": true,
  "fbCrossPostId": "1284470912345678",
  "captionCreatedAt": "2026-07-18T14:02:11.000Z",
  "captionIsEdited": false,
  "likeCountsHidden": false,
  "allImageVariants": [
    { "url": "https://scontent.cdninstagram.com/v/t51.2885-15/512094871_1080x1350_n.jpg", "width": 1080, "height": 1350 },
    { "url": "https://scontent.cdninstagram.com/v/t51.2885-15/512094871_750x938_n.jpg", "width": 750, "height": 938 },
    { "url": "https://scontent.cdninstagram.com/v/t51.2885-15/512094871_640x800_n.jpg", "width": 640, "height": 800 },
    { "url": "https://scontent.cdninstagram.com/v/t51.2885-15/512094871_320x400_n.jpg", "width": 320, "height": 400 }
  ],
  "imageVariantCount": 4,
  "childPostsCount": 1,
  "scrapedAt": "2026-07-25T09:41:07.000Z",
  "parentData": {
    "name": "natgeo",
    "type": "profile",
    "url": "https://www.instagram.com/natgeo/"
  },
  "section": "posts"
}
```

A profile row from a `details` run, showing the declared-unavailable contract:

```json
{
  "id": "787132",
  "username": "natgeo",
  "fullName": "National Geographic",
  "biography": null,
  "externalUrl": null,
  "followersCount": null,
  "followsCount": null,
  "postsCount": null,
  "isVerified": true,
  "isPrivate": false,
  "isBusinessAccount": null,
  "profilePicUrl": "https://scontent.cdninstagram.com/v/t51.2885-19/44884218_150x150_n.jpg",
  "profilePicUrlHD": "https://scontent.cdninstagram.com/v/t51.2885-19/44884218_1080x1080_n.jpg",
  "category": null,
  "inputUrl": "https://www.instagram.com/natgeo/",
  "profileUrl": "https://www.instagram.com/natgeo/",
  "detailsSource": "post_feed_user_object",
  "unavailableFields": [
    "biography",
    "externalUrl",
    "followersCount",
    "followsCount",
    "postsCount",
    "isBusinessAccount",
    "category"
  ],
  "unavailableReason": "Instagram retired /api/v1/users/web_profile_info/ (HTTP 429 from every exit; HTTP 400 even inside a live logged-in browser). Follower/following/post counts, bio, external URL, category and the business flag have no keyless HTTP source and are reported as null rather than guessed.",
  "scrapedAt": "2026-07-25T09:41:12.000Z",
  "section": "profiles"
}
```

A mention row, then an uncharged accounting row for a private account:

```json
{
  "type": "mention",
  "mentionedUsername": "carstenpeter",
  "postShortCode": "DK7pQzvNXa1",
  "postUrl": "https://www.instagram.com/p/DK7pQzvNXa1/",
  "ownerUsername": "natgeo",
  "timestamp": "2026-07-18T14:02:11.000Z",
  "inputUrl": "https://www.instagram.com/natgeo/",
  "scrapedAt": "2026-07-25T09:41:15.000Z",
  "section": "mentions"
}
```

```json
{
  "type": "accounting",
  "target": "https://www.instagram.com/some_private_account/",
  "targetKind": "profile",
  "resultsType": "posts",
  "errorReason": "EMPTY_FEED_PRIVATE_OR_RESTRICTED",
  "message": "Instagram returned HTTP 200 with 0 items for this profile. The account is private, restricted, age-gated or has no public posts.",
  "itemsReturned": 0,
  "inputUrl": "https://www.instagram.com/some_private_account/",
  "scrapedAt": "2026-07-25T09:41:19.000Z",
  "section": "accounting"
}
```

### How does it work?

Instagram serves its own web app from JSON endpoints, and this Actor calls those endpoints directly instead of rendering pages. It loads a real Instagram page once to harvest the public app id and the CSRF token, then reads a profile's feed from `/api/v1/feed/user/{username}/username/` at 12 items per request, and a profile's reels from `/api/v1/clips/user/`. If the primary feed surface fails, it resolves the numeric user id from a live surface and retries through Instagram's Relay timeline GraphQL query, mapping that response onto the same item shape so there is only ever one output contract.

Requests start direct and escalate to Apify datacenter, then Apify residential proxies, only when Instagram returns a blocking status. There is no browser and no CAPTCHA solving.

Because the Actor parses structured API responses rather than rendered markup, an Instagram front-end redesign does not affect it and your field names stay put. Only publicly visible data is collected — no account, cookie or session is used, and a private account returns a typed accounting row rather than data.

### Integrations

Instagram API Scraper is an Apify Actor, so it works with anything that can call the Apify API or consume a dataset — Apify schedules and webhooks, the Python and Node.js clients, and every no-code platform with an Apify connector.

#### Calling Instagram API Scraper from Python

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")

run = client.actor("<YOUR_USERNAME>/instagram-api-scraper").call(run_input={
    "directUrls": ["https://www.instagram.com/natgeo/", "humansofny"],
    "resultsType": "posts",
    "resultsLimit": 100,
    "onlyPostsNewerThan": "30 days",
})

for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    if row.get("errorReason"):
        print("skipped:", row["target"], row["errorReason"])
        continue
    print(row["ownerUsername"], row["shortCode"], row["likesCount"], row["commentsCount"])
```

Works in Go, Ruby, Node.js, cURL — any language that can make an HTTP request. Branch on `section`, or on the presence of `errorReason`, to route each row shape to the right handler.

#### No-code tools (n8n, Make, LangChain)

In n8n, use the Apify node — or an HTTP Request node pointed at the Actor's run endpoint with your token — and pass the same JSON input shown above; a Switch node on `section` then splits posts, profiles, mentions and accounting rows into separate branches. In Make, the Apify module supports run-and-wait, so a weekly competitor sweep can feed a Google Sheets or Airtable step directly. In LangChain, wrap the run endpoint as a tool and hand the returned rows to the agent unchanged — they are already typed JSON, so no output parser is needed. Apify schedules and webhooks cover recurrence and downstream triggers.

### Is it legal to scrape Instagram posts and profiles?

Scraping publicly accessible content is broadly treated as permissible where no authentication is bypassed, and Instagram API Scraper reads only what a signed-out visitor is served — no account, no cookie, no session. Private accounts are not accessible: they return HTTP 200 with zero items, which the Actor reports as `EMPTY_FEED_PRIVATE_OR_RESTRICTED` rather than data.

**This output contains personal data.** These fields identify or describe real people and fall under the GDPR in the EU and UK and the CCPA/CPRA in California: `ownerUsername`, `ownerFullName`, `ownerId`, `ownerIsVerified`, `caption` (user-written text that can name and describe people), `mentions` and `mentionedUsername`, `username`, `fullName`, `profilePicUrl`, `profilePicUrlHD`, `profileUrl`, and every `latestComments[]` entry including `ownerUsername`, `ownerProfilePicUrl` and the nested `owner` object. `biography`, `followersCount` and `followsCount` remain in the output contract but are always `null` in this Actor.

You therefore need a lawful basis before you store or reuse these rows — consent, or a legitimate interest you have actually assessed and documented — and data minimisation should be applied in practice: keep only the fields your use case needs, drop `caption` and `latestComments` when you are only counting engagement, use **Storage → Export dataset → Omit fields** to strip identity fields at export, and set a retention period instead of keeping snapshots indefinitely. Aggregate analysis of engagement metrics carries far less exposure than building a searchable profile database, and data subjects keep their access and erasure rights over whatever you hold.

Consult legal counsel if your use case involves bulk storage of personal data, cross-border transfer, or any form of profiling.

### ❓ Frequently asked questions

#### What Instagram fields does Instagram API Scraper return?

The five most used are `caption`, `likesCount`, `commentsCount`, `timestamp` and `ownerUsername`. A post or reel row carries 34 keys, plus `parentData` when you enable it, and the `allImageVariants` and `childPosts` arrays add nested detail underneath. Profile, mention and accounting rows have their own shapes — see the data fields section above for all of them.

#### Does Instagram API Scraper require an Instagram account or login?

No. No Instagram account, no login, no cookie, no session token. The Actor harvests only Instagram's public app id and a CSRF token from a normal page load, exactly as a signed-out browser does. The one credential you need is your Apify token. This is also the reason comments, stories and search are unavailable — those surfaces require a session, and the Actor reports that instead of faking it.

#### How many posts or reels can I extract in one run?

Up to 2400 per target, and `resultsLimit` is per target rather than a run total — so ten profiles at 2400 is up to 24,000 rows in one run. The 2400 ceiling is the engine's, not Instagram's: pagination runs 12 items per request across a maximum of 200 pages, and the input schema's `maximum` is set to that same number so the advertised limit is one the code can actually reach. Apify rejects a larger value before the run starts rather than silently clamping it.

#### What happens if a profile is private, or a target is login-walled?

You get an uncharged accounting row instead of a charged empty one. A private, restricted or age-gated account returns HTTP 200 with zero items, which becomes `errorReason: "EMPTY_FEED_PRIVATE_OR_RESTRICTED"`. A single post URL, reel URL, hashtag URL, location URL, `resultsType: "comments"` or `resultsType: "stories"` returns its own typed reason — `POST_DETAIL_LOGIN_WALLED`, `HASHTAG_LOGIN_WALLED`, `LOCATION_LOGIN_WALLED`, `COMMENTS_LOGIN_WALLED` or `STORIES_LOGIN_WALLED` — with a `message` naming exactly which endpoint refused. To get a specific post's data, scrape its owner's profile: profile feeds are fully available. In code, filter clean rows with `row.get("errorReason") is None`.

#### Why are `followersCount` and `biography` always null?

Because no keyless Instagram endpoint serves them any more. The surface that carried follower, following and post counts, the bio, the external URL, the category and the business flag was `/api/v1/users/web_profile_info/`, and the Actor's build-time probe found it returning HTTP 429 from every network exit and HTTP 400 even when replayed inside a live logged-in browser session. Rather than substituting zeros, `resultsType: "details"` returns those seven fields as `null`, names them in `unavailableFields`, and explains the cause in `unavailableReason`. Everything the post feed's user object does carry — id, username, full name, verification, private flag, and both profile picture resolutions — is returned normally.

#### Can I scrape multiple Instagram profiles at once?

Yes. `directUrls` is a list. Paste as many profile URLs or bare usernames as you need, one per line, or use Bulk edit. Plain strings and `{ "url": "..." }` objects are both accepted, and `resultsLimit` applies to each target independently. Targets are processed sequentially with a short randomised pause between them.

#### Does Instagram API Scraper work with Claude, ChatGPT and other AI agent tools?

Yes. It is callable as a standard HTTP-triggered Apify Actor run, so LangChain, CrewAI, n8n or a hand-written tool definition can invoke it and receive typed JSON with no parsing step. Rows are tagged with `section`, and failures carry `errorReason`, so an agent can branch on either without inspecting the payload shape.

#### How does Instagram API Scraper compare to other Instagram scrapers?

Checked on the Apify Store on 25 July 2026, the three most-used Instagram Actors are all published by Apify. `apify/instagram-scraper` is the broadest: its listing documents posts, reels, comments, mentions, profile details, hashtag volume and place metadata, plus search-based discovery, and it documents returning `likesCount: -1` for creators who hide their like count. `apify/instagram-profile-scraper` documents follower and following counts, bio, external URLs, related profiles and the latest 12 posts, with a paid "About profile" add-on for join and verification dates. `apify/instagram-reel-scraper` documents reel-specific fields its listing calls out — shares, views, plays, transcripts, music info and downloadable video URLs.

Those three cover surfaces this Actor deliberately does not: comments, stories, hashtag feeds, search, follower counts and video URLs are outside its scope, and it says so in typed error codes rather than empty rows. Where it differs is what it does with the response it already has. It keeps **every image size variant** (`allImageVariants`, `imageVariantCount`) instead of the first one only, emits **real carousel children** with per-child pixel dimensions and variants instead of placeholder objects, returns `likesCount` as `null` alongside a `likeCountsHidden` flag instead of a sentinel number, exposes `captionCreatedAt`, `captionIsEdited`, `productType`, `ownerIsVerified` and `fbCrossPostId` from the same payload, and pushes **uncharged accounting rows** with an eleven-value `errorReason` vocabulary for every target it could not serve. Uncharged diagnostic rows and per-variant image arrays are not documented on those three listings.

#### Does Instagram API Scraper return data in a format LLMs can use directly?

Yes. Every row is typed, normalized JSON with the same field names on every run, and nested values are plain JSON objects and arrays rather than encoded strings. No HTML parsing, no selectors. Pass a row straight into an LLM context window, index it into a vector store, or hand it to an agent tool.

#### What happens when Instagram changes its layout or anti-bot system?

The scraper is maintained, and because it reads Instagram's JSON endpoints rather than rendered markup, a front-end redesign generally does not affect it. It also carries a second path by design: if the primary feed endpoint fails, it falls back to Instagram's Relay timeline query and maps that response onto the same item shape. If a surface goes away entirely, the run writes an uncharged accounting row rather than degrading into fabricated or silently empty data. Your field names and types stay the same on your end.

#### Can I use Instagram API Scraper without managing proxies or browser infrastructure?

Yes, and by default you use no proxy at all. The Actor starts on a direct connection, and only if Instagram returns a blocking status does it climb to an Apify datacenter proxy and then to Apify residential with sticky sessions, rebuilding its session behind each new exit. You never create a proxy account, rotate an IP or run a browser. Setting a group in `proxyConfiguration` forces a starting tier, and a country code localises the residential rung.

#### Which Instagram fields work best for AI training data and RAG indexing?

For RAG indexing: `caption` is by far the highest-information text field per row and chunks cleanly, with `hashtags` and `mentions` as ready-made metadata filters and `ownerUsername` plus `url` as citation anchors. For training data and time-series work: `likesCount`, `commentsCount`, `timestamp`, `type`, `productType`, `childPostsCount` and `imageVariantCount` are the most structurally consistent fields across records. All values arrive as typed primitives, arrays or plain nested objects, so no normalization pass is needed — but note that missing values are `null`, so decide up front whether your pipeline imputes or drops them.

### 🔗 Related scrapers

| Scraper Name | What it extracts |
| ----- | ----- |
| Instagram Profile Scraper | Profile-level fields for public Instagram accounts |
| Instagram Hashtag Scraper | Instagram content discovered from a hashtag |
| Instagram Mentions Scraper | Public Instagram posts that tag a target profile |
| Instagram Related Person Scraper | Related and suggested accounts around an Instagram profile |
| Instagram Comment Leads Scraper | Commenter identities from Instagram posts |
| Threads User Posts Scraper | Posts from a Threads account, which shares the Instagram username |

### 💬 Your feedback

Found a bug, or need a field that is in Instagram's payload but not yet in the output? Open an issue on the Actor's Issues tab. Reports that include the exact input JSON and the target username are the fastest to reproduce and fix — and if a surface this README calls login-walled starts answering signed-out requests again, that is worth reporting too.

# Actor input Schema

## `directUrls` (type: `array`):

One or more Instagram URLs or bare usernames.

**Works logged-out:**
• 👤 Profiles — `https://www.instagram.com/humansofny/` or just `humansofny`

**Login-walled by Instagram (returns an uncharged accounting row, never a charged empty row):**
• 📷 Single posts — `https://www.instagram.com/p/<shortcode>/`
• 🎬 Single reels — `https://www.instagram.com/reel/<shortcode>/`
• #️⃣ Hashtags — `https://www.instagram.com/explore/tags/<tag>/`

To get a post's data, scrape its owner's profile instead — profile feeds are fully available.

## `resultsType` (type: `string`):

Output type for each target.

**Available logged-out:**
• 📷 **posts** — a profile's post feed (photos, videos, carousels)
• 🎬 **reels** — a profile's reels feed
• 🔖 **mentions** — `@mentions` mined from a profile's post captions
• 🪪 **details** — profile identity fields. Note: follower/following/post counts, bio, external URL and category have **no keyless source** since Instagram retired its profile-info endpoint; they are returned as `null` and listed in `unavailableFields`.

**Login-walled by Instagram — these return an uncharged accounting row with a typed `errorReason`, not rows:**
• 💬 **comments** — no comment data reaches logged-out clients
• 📖 **stories** — require an authenticated session

## `resultsLimit` (type: `integer`):

Maximum items to return for each target. The engine paginates 12 items per request up to 200 pages, so **2400 is the true ceiling** — the schema maximum matches the engine rather than advertising a number the code cannot reach.

## `onlyPostsNewerThan` (type: `string`):

Limit how far back to scrape. Accepts an absolute date (`YYYY-MM-DD`) or a relative window (`7 days`, `2 months`, `3 years`). Only items posted on or after this cutoff are returned. Leave empty for no filter. All times are UTC.

## `addParentData` (type: `boolean`):

If enabled, each result item also includes a `parentData` field with the source profile info.

## `search` (type: `string`):

⚠️ Instagram's search endpoint 302-redirects to the login page for logged-out clients, so this returns an uncharged accounting row rather than results. Kept for input compatibility.

## `searchType` (type: `string`):

What the search query targets. Only used if `search` is set — see the note above.

## `searchLimit` (type: `integer`):

Number of search results to return. Only used if `search` is set — see the note above.

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

**Default: no proxy.** Instagram's live endpoints answer a direct request from the Apify container, so no proxy is needed or paid for on a normal run.

If Instagram pushes back (HTTP 401/403/407/429/451/503) the actor escalates automatically: direct → datacenter → residential (3 retries, then sticky residential). Enable Apify Proxy below only if you want to force a specific tier.

## Actor input object example

```json
{
  "directUrls": [
    "https://www.instagram.com/humansofny/"
  ],
  "resultsType": "posts",
  "resultsLimit": 10,
  "addParentData": false,
  "search": "",
  "searchType": "hashtag",
  "searchLimit": 1,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

All scraped items in the Actor's default dataset, shown with every post/reel field.

## `overview` (type: `string`):

A compact subset of post fields, for a quick scan.

## `reels` (type: `string`):

Reels-only rows from a profile's reels feed.

## `profiles` (type: `string`):

Profile identity fields (username, verification, bio where available). Follower/following/post counts, bio, external URL and category have no keyless source and are null.

## `mentions` (type: `string`):

@mentions mined from a profile's post captions.

## `search` (type: `string`):

Search results, when Instagram's search endpoint is reachable for the query. Login-walled for logged-out clients as of 2025, so this is usually empty accounting rows instead.

## `accounting` (type: `string`):

Uncharged rows explaining blocked, login-walled, private or empty targets — never billed.

## `comments` (type: `string`):

Comment rows, for when Instagram's comment surfaces are reachable logged-out. Currently always login-walled, so `resultsType=comments` always returns an uncharged accounting row instead — this view stays empty.

# 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 = {
    "directUrls": [
        "https://www.instagram.com/humansofny/"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scraper-engine/instagram-api-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 = {
    "directUrls": ["https://www.instagram.com/humansofny/"],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("scraper-engine/instagram-api-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 '{
  "directUrls": [
    "https://www.instagram.com/humansofny/"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call scraper-engine/instagram-api-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scraper-engine/instagram-api-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/ZH7T91ZzUsOedFpIy/builds/tfdmnktvlPPHwdcPo/openapi.json
