SocialCrawl

Instagram

Instagram profiles, posts, reels, comments, stories, highlights, followers, hashtags, locations, and audio through one API key

Public Instagram data without a Meta app review: profiles and their whole feed, reels with view and share counts, comment threads, story trays and saved highlights, the follower graph, hashtag and location feeds, and the audio library. Handles are passed without the leading @.

Base URL: /v1/instagram/...

Share counts and follower lists come from a heavier upstream than the rest of the platform. /v1/instagram/post carries no share count and no cheap endpoint does. Use /v1/instagram/post/stats for one post, or /v1/instagram/profile/posts/full and /v1/instagram/profile/reels/full for a whole feed. View counts are different: they stay on the 1-credit reads, with post, profile/posts, and profile/reels all carrying them on video.

Quickstart

1. Fetch a profile

cURL
curl "https://www.socialcrawl.dev/v1/instagram/profile?handle=instagram" \
  -H "x-api-key: YOUR_API_KEY"

2. Fetch their content

cURL
curl "https://www.socialcrawl.dev/v1/instagram/profile/posts?handle=instagram" \
  -H "x-api-key: YOUR_API_KEY"

3. Read computed fields

When an endpoint supports a computed field and the required source inputs are present, the unified response includes that optional field. Depending on the endpoint, optional fields can include engagement_rate, language, content_category, and estimated_reach. See Computed fields for formulas, clamping rules, and null semantics. Where an endpoint supports them, list rows also carry judged labels (computed.labels, computed.relevance) by default at no extra credits; see Labels.

Accounts

EndpointCreditsWhat it returnsKey parameters
GET /v1/instagram/profile1Bio, exact integer follower count, following and post counts, avatar, verificationhandle, trim
GET /v1/instagram/basic-profile1Name and picture only, for when all you hold is a numeric iduserId
GET /v1/instagram/profile/full5Profile plus recent posts plus computed analytics in one flat callhandle, posts (1-100, default 25, at most 12 per call), include
GET /v1/instagram/engagement5An overall engagement rate, totals, and a per-post breakdownhandle
GET /v1/instagram/followers5 or 10Who follows this account, about 50 a pagehandle or user_id, cursor, coverage
GET /v1/instagram/following5 or 10Who this account follows, about 50 a pagehandle or user_id, cursor, coverage
GET /v1/instagram/similar5The accounts Instagram itself suggests as comparable, one fixed listhandle or user_id
GET /v1/instagram/user/embed1An embeddable HTML snippet for dropping a profile onto your own pagehandle

profile also returns author.last_post_at, the publish time of the newest post among the recent posts the profile read carries, as an ISO 8601 timestamp, at no extra cost. Pinned posts count, and the newest is taken whatever the order. It is null for a private account, for an account with no posts, and when the profile read returned no recent posts, so treat null as unknown rather than as inactive.

profile/full collapses the first two Quickstart calls into one and adds two average engagement rates, posting cadence, top post, and format mix. One call reads one page of recent posts, which on Instagram is 12, and computes everything over that page: posts can shorten the window but a value above 12 still returns 12, with a _warnings note, and posts_cursor sent back as cursor reads the next page for another 5 credits. computed.avg_engagement_rate is view-based, the mean of (likes + comments + shares) / views per post, and Instagram publishes no view count on photos and carousels, so on a mostly-photo grid it is null. computed.avg_engagement_rate_by_followers divides the mean likes and comments in that window by the follower count instead, the same formula as computed.engagement_rate on profile. It is null when the follower count is missing and when no post in the window carries a like or comment count, for example an account that hides its likes. include=computed only omits the raw posts array from the response; the metrics are computed from the same posts either way. If the posts leg fails the profile still comes back, with the post-dependent metrics null.

Posts and reels

EndpointCreditsWhat it returnsKey parameters
GET /v1/instagram/profile/posts1The cheap feed pull: likes, comments, and views on videohandle, next_max_id, trim, label, judgments
GET /v1/instagram/profile/reels1The same for reelshandle or user_id, max_id, trim, label, judgments
GET /v1/instagram/profile/posts/full5 to 25The feed plus a per-item share count and a coverage figurehandle or user_id, limit (max 50)
GET /v1/instagram/profile/reels/full5 to 25The same for reelshandle or user_id, limit (max 50)
GET /v1/instagram/post1Caption, media, tagged users, engagement, and the play count on videourl, download_media
GET /v1/instagram/post/stats5Adds the paper-plane share count on videourl
GET /v1/instagram/post/likers5A ranked sample of who liked a posturl
GET /v1/instagram/media/transcript10The spoken words in a video or reelurl
GET /v1/instagram/tagged5Posts other people tagged an account in, a different question from what it postedhandle or user_id, cursor

The two full variants are metered: they consume upstream pages of 12 items and bill 5 credits per page, from 5 up to 25 for a limit=50 walk. Both need a handle for the share leg. A user_id-only call still returns views, likes and comments, with shares null and a partial refund.

Instagram removed play counts from its public web pages in August 2026, so post now fills the count from a second source inside the same call. You still make one request and pay one credit.

Comments

EndpointCreditsWhat it returnsKey parameters
GET /v1/instagram/post/comments5A post's comment section, ranked by popularity by defaulturl, sort, cursor, label, judgments
GET /v1/instagram/post/comment/replies1One comment's replies, not a second copy of the sectionurl and comment_id, both required; optional label, judgments
GET /v1/instagram/comment5 or 15One named comment, or comments matching an author or a snippetcomment_url, or post_url plus comment_id

Pass sort=recent whenever you intend to walk a whole thread. Instagram only ranks the most-liked head of the list, so a top walk starts repeating comments once you page past it. Page deeper with the next_cursor from each response.

GET /v1/instagram/comment is a bounded server-side scan rather than a direct fetch, because Instagram exposes no fetch-one-comment read. It costs 5 credits, or 15 with deep_scan for comments buried deep in a large thread. Passing back the lookup.position_hint from an earlier call replays the last-known sort chain first, which makes re-checking a comment you have already found much cheaper.

Stories and highlights

EndpointCreditsWhat it returnsKey parameters
GET /v1/instagram/stories5The tray a user has live right nowhandle or user_id
GET /v1/instagram/story/download5One story's full-resolution mediauser_id and story_id
GET /v1/instagram/highlights1The saved collections pinned to a profilehandle or user_id
GET /v1/instagram/highlight/detail1One highlight opened by idid

An account with nothing live returns an empty list rather than an error. Highlights are the permanent version of the same thing.

Search and discovery

EndpointCreditsWhat it returnsKey parameters
GET /v1/instagram/search1Instagram's own mixed search box: accounts, hashtags, and placesquery
GET /v1/instagram/search/profiles1Accounts found by bio or caption keywordquery, cursor
GET /v1/instagram/search/popular1The popular-posts feed for a keywordquery, cursor
GET /v1/instagram/search/reels1Reels matching a keyword or phrase, page-paginatedquery, date_posted, page, region, country, include, label, relevance, judgments
GET /v1/instagram/search/hashtag5A tag feed, with type selecting top, recent, or clipshashtag, type, cursor
GET /v1/instagram/search/location5Turns a place name into location.pk for location_idquery
GET /v1/instagram/location/posts5What was posted at that place, newest first, about 60 a pagelocation_id, cursor
GET /v1/instagram/reels/trending5A global sample of Instagram's public trending pagenone
GET /v1/instagram/username-suggestions5Invented available handle ideas from a keywordquery

search/profiles reads Google's index of Instagram rather than Instagram's own account search, so treat it as discovery rather than a lookup. username-suggestions is unrelated to it despite the name: it does not look anything up. reels/trending takes no parameters, sends a small batch per call, and repeats items, so call again to see more.

Neither trending endpoint takes a region. Instagram builds the trending reels feed and the trending music chart for the account viewing them, not for a place, so there is no country to pass and none is planned. For posts from one region, look up places with search/location (a city, a neighbourhood, a venue) and page each place's posts with location/posts, or send region to search/reels (below).

Hashtag feeds: only recent pages

GET /v1/instagram/search/hashtag takes type=top (the default), type=recent, or type=clips. Only type=recent paginates: forward the next_cursor from each response and send type=recent again with it. top and clips are one ranked page each, so they come back with has_more: false and no cursor. To cover more ground with those rankings, query more hashtags rather than more pages.

Reel search ordering is relevance, not a stable list

GET /v1/instagram/search/reels returns Instagram's own relevance ranking for the keyword, and that ranking is live. The same query re-run minutes later can come back with a different, partially overlapping set of reels, and pages can overlap at their boundaries. No parameter pins an ordering, and date_posted narrows the window rather than sorting it.

date_posted accepts last-week, last-month, and last-year. last-day and last-hour stopped being served around 26/08/2026, and a request carrying either is rejected before any call is made, at no charge, with an error that names the three values that work. Note that a filtered search returns engagement.views as null, because the only surface that offers a date filter no longer carries play counts; if you need both a date window and view counts, omit date_posted and filter on published_at yourself.

Build on that rather than against it. Rank on engagement.views in your own code instead of trusting result order, deduplicate by post.id across pages and across runs, and if you need a stable view of a keyword over time, accumulate results into your own store across repeated runs. For a creator's complete back catalogue in a fixed order, walk profile/reels instead: a profile listing is stable in a way a relevance search cannot be.

GET /v1/instagram/search/reels takes three optional parameters. Each works alone, and they combine. Without them the call is unchanged.

cURL
curl "https://www.socialcrawl.dev/v1/instagram/search/reels?query=restaurantes&region=ES&country=ES" \
  -H "x-api-key: YOUR_API_KEY"
  • region (ES, MX, DE, BR, KR or FR) localises the results. Instagram does not rank search by country, so the market's own name is added to your query: fitness with region=ES searches fitness españa. A query that already names the market, or is already written in the market's own script (Hangul for KR), which Instagram localises by itself, is sent as it is. data.region.query_sent shows the query that ran. Same price as a plain call. It favours creators from that market but does not promise every creator is from it.
  • include=creator adds each reel's creator card from the creator's About this account panel: post.ext.author_country, author_followers, author_following, author_posts_count, author_public_email and author_public_phone. It never drops, adds or reorders rows. 2 extra credits per creator looked up; two reels by one creator are one lookup, a creator that cannot be found is refunded, and a creator looked up in the last 15 minutes is free. data.hydration reports the lookups and credits.
  • country keeps only the reels whose creator declares that market, and reports what it dropped in data.country (kept, dropped_other, dropped_unknown). It adds the creator card at the same price, charged for every creator looked up whether their reels are kept or not, and localises the query too when region is not sent. A page where no creator matches returns items: [] with the warning country_no_match in data._warnings, and its lookups are still billed. A page left empty only because the creator lookups failed costs nothing.

author_country is the country the creator declares on Instagram, not where the reel was filmed. A null means Instagram does not publish one for that creator, and nothing is guessed. The same parameter names mean the same thing on TikTok search; only the way region works differs, because TikTok searches from the market and Instagram needs the query localised.

Topics about a place, such as travel or eSIMs, are often made by visitors, so region alone finds fewer local creators there. A phrasing locals use helps (esim móvil rather than esim), and country keeps only the market's creators. Any other market code, or a typo in any of the three, is refused at no charge with the list of valid values.

post.ext.author_followers is null on a plain call, because Instagram stopped sending follower counts in its search payload in August 2026. include=creator fills it. For creators you already hold, GET /v1/instagram/profile returns author.followers, and POST /v1/prism/profiles resolves up to 50 handles per call at 1 credit per resolved profile, failed rows refunded. One call can mix platforms, so TikTok and Instagram creators resolve together. Match rows to your input by each row's index.

Audio

EndpointCreditsWhat it returnsKey parameters
GET /v1/instagram/search/music5Tracks matching a name, each with an audio idquery, cursor
GET /v1/instagram/music/trending5A chart of licensed tracks trending nownone
GET /v1/instagram/audio/reels1The reels using one sound, cursor-pagedaudio_id, cursor

audio_id is the number in an instagram.com/reels/audio/{audio_id}/ URL. That chain is how you measure a sound's spread rather than its chart position.

All endpoints

엔드포인트 38개를 제공해요.

엔드포인트경로크레딧 등급
List Instagram reels using an audio track/v1/instagram/audio/reelsstandard (1cr)
Get Instagram basic profile/v1/instagram/basic-profilestandard (1cr)
Get Instagram highlight detail/v1/instagram/highlight/detailstandard (1cr)
List Instagram story highlights/v1/instagram/highlightsstandard (1cr)
Get Instagram post details/v1/instagram/poststandard (1cr)
List replies under an Instagram comment/v1/instagram/post/comment/repliesstandard (1-5cr)metered
Get Instagram user profile/v1/instagram/profilestandard (1cr)
Get Instagram account transparency details/v1/instagram/profile/aboutstandard (1cr)
Instagram profile, recent posts, and computed analytics in one call./v1/instagram/profile/fullstandard (5cr)
List Instagram user posts/v1/instagram/profile/postsstandard (1-17cr)metered
List Instagram user reels/v1/instagram/profile/reelsstandard (1-18cr)metered
Search Instagram accounts, hashtags, and places/v1/instagram/searchstandard (1cr)
Search popular Instagram posts/v1/instagram/search/popularstandard (1-13cr)metered
Search Instagram profiles by keyword/v1/instagram/search/profilesstandard (1-25cr)metered
Search Instagram reels/v1/instagram/search/reelsstandard (1-69cr)metered
Get Instagram user embed HTML/v1/instagram/user/embedstandard (1cr)
Look up one Instagram comment by URL or id/v1/instagram/commentadvanced (5-15cr)metered
Get Instagram engagement statistics/v1/instagram/engagementadvanced (5cr)
List Instagram followers/v1/instagram/followersadvanced (5-10cr)metered
List Instagram following/v1/instagram/followingadvanced (5-10cr)metered
List recent posts at an Instagram location/v1/instagram/location/postsadvanced (5cr)
Get Instagram post on-screen text/v1/instagram/media/screen-textadvanced (5cr)
List trending Instagram music/v1/instagram/music/trendingadvanced (5cr)
List Instagram post comments/v1/instagram/post/commentsadvanced (5-19cr)metered
List Instagram post likers/v1/instagram/post/likersadvanced (5cr)
Get Instagram post stats including the share count/v1/instagram/post/statsadvanced (5-9cr)metered
Instagram posts with views, likes, comments, and per-post share counts where available, in one call./v1/instagram/profile/posts/fulladvanced (5-25cr)metered
Instagram reels with views, likes, comments, and per-reel share counts where available, in one call./v1/instagram/profile/reels/fulladvanced (5-25cr)metered
Get trending Instagram reels/v1/instagram/reels/trendingadvanced (5cr)
Search Instagram posts by hashtag/v1/instagram/search/hashtagadvanced (5cr)
Search Instagram locations/v1/instagram/search/locationadvanced (5cr)
Search Instagram music/v1/instagram/search/musicadvanced (5cr)
List similar Instagram accounts/v1/instagram/similaradvanced (5-85cr)metered
List an Instagram user's active stories/v1/instagram/storiesadvanced (5cr)
Download a single Instagram story/v1/instagram/story/downloadadvanced (5cr)
List posts an Instagram user is tagged in/v1/instagram/taggedadvanced (5cr)
Get Instagram username suggestions/v1/instagram/username-suggestionsadvanced (5cr)
Get Instagram media transcript/v1/instagram/media/transcriptpremium (10cr)

Platform notes

Media URLs expire. The media_urls on any post are short-lived signed CDN links. For archiving, call post with download_media=true and read the durable links from data.post.ext.download_media_urls, each entry carrying post_id, cdn_url, type, and cached. It adds a few seconds of latency while the media is fetched.

Likers are a ranked sample, now about a thousand rows. post/likers returns a ranked slice of a post's likers in one response, regardless of how many likes the post has, at 5 credits. On 14/09/2026 a post with 94,090 likes returned 1,019 of them; earlier releases returned roughly the first 100. The full like count is in the same response as data.total, and Instagram exposes no cursor on this surface, so the list is a sample rather than the whole thing.

Follower and following lists, and coverage=full. A plain walk of followers or following can end with has_more: false before the profile's count. On most accounts it now ends within a few rows of that count; on large verified accounts Instagram caps every route at about 50 rows with no cursor, and a plain walk ends there. data.total on every page carries the profile's own count, so compare the rows you collected against it. Send coverage=full with handle to walk a merged list instead: each page is drawn from several reads of the list and carries no account twice inside a page, until the rows reach the count or no read has more. Across consecutive pages a handful of accounts can repeat on the pages one read covered on its own; wherever a single read cannot cover the list, the merged walk runs instead and never returns an account it has already given you. A full-coverage page is quoted at 10 credits instead of 5 and settled back down to 5 on any page a single read covered in full, so the mode costs its full price only on the accounts that need it. A page carries about 50 accounts and usually takes about 8 seconds, occasionally up to 45. A full list therefore costs at most roughly its account count divided by 50, times 10 credits: on 13/09/2026 a following list of 2,652 took 54 pages and a follower list of 2,360 took 59, since the later pages of a follower walk add fewer new accounts. Keep coverage=full on every call of the walk, since its cursor only continues that mode, and retry a 503 with the same cursor.

cURL
curl "https://www.socialcrawl.dev/v1/instagram/following?handle=billboard&coverage=full" \
  -H "x-api-key: YOUR_API_KEY"

A private account is a 404. followers and following return 404 RESOURCE_NOT_FOUND with details.reason: account_private, refunded, when the account exists but its lists are hidden.

Like and comment counts are Instagram's own. For a post also shared to Facebook, the likes and comments it collected on Facebook are not included in post.engagement, whichever source answers. That is the figure instagram.com shows under the post. Views follow the same rule.

Photo posts have no views and no shares. Both come back null on post/stats, which is Instagram's behaviour rather than a gap.

A location grid pages back in time. location/posts returns about 60 posts a page, newest first, and pagination.next_cursor fetches the next, older page until has_more is false. How far one page reaches depends on the place: a busy landmark fills a page in about a day, a neighbourhood covers a week or two, and a quiet spot can span years. Instagram orders the grid by when each post was created, so a scheduled post that went live later sits among older posts, while its published_at shows the real publish time. On 13/09/2026 that was about 1 post in 75, up to 13 days apart. To collect a time window, keep paging until a whole page is older than the window's start and filter on published_at, rather than stopping at the first older post. Each page is a separate call at 5 credits, and a page takes about 5 to 12 seconds.

music/trending reflects the viewing account's market. Instagram serves its music chart to an account, and some accounts are shown Instagram's royalty-free sound library instead of a chart. The endpoint returns only a licensed chart; when none comes back it answers 503 with your credits refunded, so retry after a minute. The market a chart belongs to is not exposed and cannot be selected.

safe_url=true where you are embedding. Several endpoints accept it and return URL-safe profile picture and media links, suitable for dropping straight into a page.

Carousels return every slide. A sidecar post's media_urls is the full array, not just the cover image.

trim=true costs the same and returns the full list. profile, profile/posts, and profile/reels accept it and return the same rows at the same price. On the two feed endpoints the only differences are that post.ext.coauthors is omitted and post.flags.pinned is null, because the trimmed record carries neither signal; every other field is unchanged.

post.id is the exact 19-digit media id. On profile/reels, tagged, and stories the last two or three digits were rounded on every row in earlier releases, and tagged page 2 repeated page 1. If you stored ids from those three endpoints before this change, re-sync them. Ids from profile/posts, search/hashtag, and location/posts were always exact.

A handle that does not exist is a 404, not an empty list. profile, followers, and following return 404 RESOURCE_NOT_FOUND with details.reason: handle_unresolved, refunded, when the account cannot be resolved. An existing account whose list is empty still returns 200 with items: [].

Labels

These lists label their rows by default, at no extra credit. On /v1/instagram/post/comments and /v1/instagram/post/comment/replies, each comment carries sentiment, question, purchase_intent and complaint on computed.labels. On /v1/instagram/profile/posts, /v1/instagram/profile/reels and /v1/instagram/search/reels, each row carries sponsored, intent and niche. /v1/instagram/search/reels also scores each row's relevance to your query on computed.relevance, without dropping or reordering anything; add relevance=filter to drop the rows that are about something else, still free. Send judgments=off for the page without them. See Labels for the field shapes, rows still pending, and the labels you can add with label=.

Next steps