Instagram API Scraper avatar

Instagram API Scraper

Pricing

from $4.99 / 1,000 results

Go to Apify Store
Instagram API Scraper

Instagram API Scraper

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

Pricing

from $4.99 / 1,000 results

Rating

0.0

(0)

Developer

Scraper Engine

Scraper Engine

Maintained by Community

Actor stats

0

Bookmarked

34

Total users

8

Monthly active users

22 days ago

Last modified

Share

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 CategorySigned-out visitorRequires 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 reelLogin — the media-info endpoint redirects to /accounts/login/
Stories and highlightsLogin — the story tray endpoints redirect to /accounts/login/
Single post / reel detail by shortcode URLLogin
Hashtag feeds, location feeds and Instagram searchLogin
Follower, following and post counts, bio, external URL, categoryNo keyless endpoint remains — see below
Private accounts, follower lists, direct messagesLogin 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.

sectionRow shapeCharged
postsOne media row per post — photo, video or carouselrow_result
reelsOne media row per reel, same shape, type forced to Videorow_result
profilesOne profile identity row per target (resultsType: "details")row_result
mentionsOne row per @mention found in a captionrow_result
accountingOne diagnostic row per blocked, walled, private or empty target❌ Uncharged
searchSearch result rows — structurally supported, login-walled in practicerow_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 NameDescription
idInstagram's numeric media id, as a string
typePhoto, Video or Sidecar (carousel). Reels rows are always Video
productTypeInstagram's own product label for the media, e.g. clips, feed, carousel_container
shortCodeMedia shortcode — the segment in instagram.com/p/<shortCode>/
urlCanonical post URL built from shortCode
captionFull caption text as written by the account owner
hashtagsArray of hashtag words parsed out of the caption, without the #
mentionsArray of usernames parsed out of the caption, without the @
likesCountLike count, or null when the owner has hidden counts
likeCountsHiddentrue when Instagram flagged like and view counts as hidden by the owner
commentsCountComment count as reported on the media, or null
isCommentsDisabledtrue/false when Instagram sends a usable signal, null when it sends none
firstCommentText of the first preview comment, or null when no comment reaches a logged-out client
latestCommentsArray of preview comment objects, or null — see the nested shapes below
timestampUTC ISO timestamp the media was posted
captionCreatedAtUTC ISO timestamp the caption itself was written, or null
captionIsEditedtrue/false when Instagram carries the flag, null when it does not
displayUrlHighest-priority image URL Instagram returned for the media
imagesFlat array of image URLs — the media's own, plus one per carousel child
allImageVariantsEvery image size variant in the payload, each with url, width, height
imageVariantCountNumber of entries in allImageVariants
dimensionsHeightOriginal media height in pixels, or null
dimensionsWidthOriginal media width in pixels, or null
altInstagram's accessibility caption for the media, or null
childPostsArray of carousel child objects — empty array for single-media posts
childPostsCountNumber of entries in childPosts
ownerUsernameAccount handle that posted the media
ownerFullNameDisplay name on the posting account
ownerIdNumeric account id, as a string
ownerIsVerifiedtrue/false when the payload carries the flag, null when it does not
fbCrossPostIdFacebook cross-post id (fbid) when Instagram sends one, otherwise null
inputUrlThe exact directUrls entry this row came from
scrapedAtUTC ISO timestamp the row was built
sectionRow shape tag — posts or reels
parentDataSource 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.

parentDataname, 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 NameDescription
idNumeric account id, as a string
usernameAccount handle
fullNameDisplay name, or null
isVerifiedVerification flag, or null when the payload omits it
isPrivatePrivate-account flag, or null when the payload omits it
profilePicUrlStandard-resolution avatar URL
profilePicUrlHDHighest-resolution avatar URL available in the payload
profileUrlCanonical profile URL
detailsSourceWhich surface the row was built from — post_feed_user_object
unavailableFieldsArray naming every field with no keyless source on this run
unavailableReasonPlain-language explanation of why those fields are null
biographyAlways null — no keyless source
externalUrlAlways null — no keyless source
followersCountAlways null — no keyless source
followsCountAlways null — no keyless source
postsCountAlways null — no keyless source
isBusinessAccountAlways null — no keyless source
categoryAlways null — no keyless source
inputUrlThe directUrls entry this row came from
scrapedAtUTC ISO timestamp the row was built
sectionRow 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:

errorReasonWhat happened
COMMENTS_LOGIN_WALLEDNo comment data reaches a logged-out client on any surface
STORIES_LOGIN_WALLEDStories need an authenticated session
POST_DETAIL_LOGIN_WALLEDA single post or reel URL was given; the media-info surface is walled
HASHTAG_LOGIN_WALLEDA hashtag URL was given; the tag feed is walled
LOCATION_LOGIN_WALLEDA location URL was given; the location feed is walled
SEARCH_LOGIN_WALLEDsearch was set; Instagram's search endpoint redirects to the login page
EMPTY_FEED_PRIVATE_OR_RESTRICTEDHTTP 200 with zero items — private, restricted, age-gated or no public posts
PROFILE_ID_UNRESOLVEDNo numeric user id could be resolved from any live surface
UNSUPPORTED_TARGET_FOR_MODEThe target kind does not fit the selected resultsType, or could not be parsed
FETCH_FAILEDEvery live surface failed for this target after retries
NO_MENTIONS_IN_CAPTIONSCaptions were scanned successfully and contained no @mention

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

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.

FeatureInstagram Graph APIInstagram 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 accountsnull, declared in unavailableFields
Output shapeMeta's documented Graph API schemaOne normalized row per media item, stable across runs
Request budgetPublished per-app limits — check Meta's docs12 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.

ParameterRequiredTypeDescriptionExample Value
directUrlsYesarrayInstagram 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"]
resultsTypeNostringWhat 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"
resultsLimitNointegerMaximum items per target. Minimum 1, maximum 2400 — the engine's real ceiling of 200 pages at 12 items per page. Default 10.100
onlyPostsNewerThanNostringOnly 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"
addParentDataNobooleanAttach a parentData object with the source profile to each post or reel row. Default false.false
searchNostringSearch 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 "".""
searchTypeNostringWhat the search query targets. One of user, hashtag, place. Default "hashtag". Only read when search is set."hashtag"
searchLimitNointegerNumber of search results to return. Minimum 1, maximum 250. Default 1. Only read when search is set.1
proxyConfigurationNoobjectApify 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

{
"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:

{
"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:

{
"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:

{
"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"
}
{
"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

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.

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.

Scraper NameWhat it extracts
Instagram Profile ScraperProfile-level fields for public Instagram accounts
Instagram Hashtag ScraperInstagram content discovered from a hashtag
Instagram Mentions ScraperPublic Instagram posts that tag a target profile
Instagram Related Person ScraperRelated and suggested accounts around an Instagram profile
Instagram Comment Leads ScraperCommenter identities from Instagram posts
Threads User Posts ScraperPosts 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.