Instagram Public Data API & Scraper
The Instagram API returns public Instagram data — profiles, posts, reels and hashtags — as clean JSON.
🤖 Using an AI assistant? Copy this link into ChatGPT / Claude / Cursor — it reads every endpoint and parameter instantly and tells you if this API fits your use case.
The primary profile endpoint returns a username's full name, biography, follower, following and post counts, verified/private/business flags, category and profile picture — no login or scraping. You can also pull a user's posts and reels, run a users_search, expand a post's info and comments, and fetch hashtag results and post audio. It is built for social-analytics tools, creator platforms and brand-monitoring products that need live public Instagram data without account pools or a fragile scraper. One ReefAPI key, one shared credit pool, the standard { ok, data, meta, error } envelope.
Every id form Instagram uses, and which one each action will accept
Instagram identifies the same post three different ways and the same account two different ways, and the values are not interchangeable. This table is the mapping, taken from measured responses on @nasa and @natgeo. The row that costs people the most time is the third: post_info returns a media_id it will not accept back.
| Id | Format | Where it comes from and what takes it |
|---|---|---|
| shortcode | 11 characters, mixed case, e.g. DcOX3hWFiey | The code in instagram.com/p/<code>/ or /reel/<code>/. Accepted by post_info and post_comments, and a full post URL works too. |
| media_id (bare) | 19-digit numeric string, e.g. 3967213292204992434 | Returned by profile, posts and reels. Accepted by post_info and post_comments. |
| media_id (from post_info) | The same number with a POLARIS_ prefix, e.g. POLARIS_3967213292204992434 | post_info returns this form. It is not accepted as input: a measured call with the prefixed value came back MISSING_PARAM. Strip POLARIS_ before sending it back. |
| user_id | Numeric string, no fixed length | 528817151 for @nasa, 787132 for @natgeo, 25025320 for @instagram. Accepted wherever an id-based lookup is offered. |
| fbid | 17 digits, starting 1784 | 17841401474538262 for @nasa. Returned only by profile. It is a second account identifier, not a substitute for user_id. |
| comment_id | 17 digits | 17914942803450269, returned by post_comments. |
| hashtag id | 17 digits | 17843701351037088 for #nasa, returned by hashtag_search and search alongside media_count 10088759. |
| audio_id | Numeric string | Read it from post_info as post.music.audio_id and feed it to audio_media. |
Instagram never hands you a shortcode for a carousel slide. carousel_media[] entries carry their own media_id, media_type, display_url and dimensions, but no shortcode of their own, because the shortcode belongs to the album as a whole.
Real request and response JSON
Captured from the indexed primary action, profile, on .
{
"method": "POST",
"url": "https://api.reefapi.com/instagram/v1/profile",
"headers": {
"x-api-key": "$REEF_KEY",
"content-type": "application/json"
},
"body": {
"username": "nike"
}
}{
"ok": true,
"meta": {
"api": "instagram",
"endpoint": "profile",
"mode": "live",
"latency_ms": 16988.3,
"record_count": 1,
"bytes": 119,
"cache_hit": false,
"method": "i.instagram.com internal public JSON",
"content_type": "application/json; charset=utf-8",
"status": 401
},
"data": {
"username": "nike",
"full_name": "Nike",
"biography": "Just Do It.",
"bio_links": [],
"pronouns": [],
"follower_count": 291770197,
"following_count": 264,
"post_count": 0,
"is_verified": true,
"is_private": false,
"is_business": false,
"bio_mentions": [],
"bio_hashtags": [],
"profile_pic": "https://scontent.cdninstagram.com/v/t[redacted-phone]/[redacted-phone]_[redacted-phone]_[redacted-phone]_n.jpg?stp=dst-jpg_s150x150_tt6&_nc_cat=1&ccb=7-5&_nc_sid=f7ccc5&efg=eyJ2ZW5jb2RlX3RhZyI6InByb2ZpbGVfcGljLnd3dy4zOTkuQzMifQ%3D%3D&_nc_ohc=seHSxG1XmiAQ7kNvwHmnIUO&_nc_oc=AdrZlYdwoK2k4WOl1OG4BLaViqAa7Bs-qy_m-5hoy8-LafzAQvwZpldCKc0a_MlDIOU&_nc_zt=24&_nc_ht=scontent.cdninstagram.com&_nc_gid=rQhcgsso9cUouGedQfFwuw&_nc_ss=7ea8c&oh=00_AQDZmik6sLizzAmmPA9fnwXLlyqU_olhT0oaxqukO2nHnQ&oe=6A61A43A",
"user_id": "13460080",
"recent_posts": [],
"timeline_page_info": {}
}
}What the Instagram Public Data API does
| Action | Description | Concrete use case | Key params |
|---|---|---|---|
| profile | public profile facts and first-page recent posts | Social-listening tools call profile to get public profile facts and first-page recent posts. | username |
| posts | public profile grid posts, newest first, with max_id pagination | Creator and influencer platforms call posts to get public profile grid posts, newest first, with max_id pagination. | username, limit, max_id, cursor, rich |
| reels | public profile reels tab, newest first, with max_id pagination | Brand-monitoring teams call reels to get public profile reels tab, newest first, with max_id pagination. | username, limit, max_id, cursor |
| similar_accounts | public related profiles from web_profile_info | Audience analysts call similar_accounts to get public related profiles from web_profile_info. | username, limit |
| search | topsearch blended public users, hashtags, and places | Social-listening tools call search to get topsearch blended public users, hashtags, and places. | query |
| users_search | public user lookup by handle (exact / near-exact match) | Creator and influencer platforms call users_search to get public user lookup by handle (exact / near-exact match). | query |
| hashtag_search | topsearch public hashtags only | Brand-monitoring teams call hashtag_search to get topsearch public hashtags only. | query |
| post_info | maximally-complete public post/reel detail by shortcode (URL) or media_id | Audience analysts call post_info to get maximally-complete public post/reel detail by shortcode (URL) or media_id. | shortcode, media_id |
| post_comments | public comments for a post/reel by shortcode (URL) or media_id, with CURSOR pagination | Social-listening tools call post_comments to get public comments for a post/reel by shortcode (URL) or media_id, with CURSOR pagination. | shortcode, media_id, limit, cursor |
| audio_media | public reels feed that use a given audio/music track, with pagination | Creator and influencer platforms call audio_media to get public reels feed that use a given audio/music track, with pagination. | audio_id, limit, max_id, cursor |
Call profile from your stack
curl -X POST https://api.reefapi.com/instagram/v1/profile \
-H "x-api-key: $REEF_KEY" \
-H "content-type: application/json" \
-d '{"username":"nike"}'import requests
r = requests.post(
"https://api.reefapi.com/instagram/v1/profile",
headers={"x-api-key": REEF_KEY},
json={
"username": "nike"
},
)
print(r.json()["data"])const res = await fetch("https://api.reefapi.com/instagram/v1/profile", {
method: "POST",
headers: {
"x-api-key": process.env.REEF_KEY,
"content-type": "application/json",
},
body: JSON.stringify({
"username": "nike"
}),
});
const { ok, data, meta, error } = await res.json();Ask your MCP-connected assistant: call reefapi.instagram.profile with {"username":"nike"}.Who uses this API and why
- Creator and influencer platforms call profile to pull follower counts, verification and bio for any public account.
- Brand-monitoring tools use hashtag_search and posts to track a campaign's reach and engagement.
- Social-analytics products use reels and post_comments to measure content performance and audience sentiment.
Questions developers ask before integrating
I passed the media_id that post_info gave me straight back and got MISSING_PARAM. Why?
post_info reports media_id with a POLARIS_ prefix, and the parameter only takes the bare number. A measured call with media_id "POLARIS_3967213292204992434" returned MISSING_PARAM ("missing: shortcode or media_id") in 32 ms, while the same call with "3967213292204992434" returned the post. The bare form is what profile, posts and reels give you, so round-tripping from those needs no handling. Store the shortcode if you want one key that works everywhere.
media_type is a word in one action and a number in another. Which is which?
posts, reels and profile.recent_posts return media_type as a string, and the measured values were "image", "video" and "carousel". post_info returns Instagram's own numeric enum instead: 1 for an image, 2 for a video, 8 for a carousel, all three confirmed on live posts. post_info also adds product_type, which is finer grained: "feed" for a normal image, "clips" for a reel, "carousel_container" for an album. Normalize at your boundary if you key on type.
How does a carousel (album) post come back?
As one post with carousel_media[] attached. A measured @natgeo album returned media_type 8, product_type "carousel_container" and five entries in carousel_media[], each with its own media_id, media_type, display_url and dimensions, and no shortcode. The album's own display_url is the first slide, so a feed that reads only display_url shows the cover and silently drops the other four. The list action flags them too: a carousel row in posts has media_type "carousel" and carries the same array.
Which fields come back null, and which are simply absent?
The distinction matters if you key on presence. Fields Instagram has no value for are usually dropped rather than nulled: video_view_count exists only on video rows (measured 480980 on one, with the key entirely missing on every image row), and tagged_users and coauthor_producers appear only when the post actually has them. category on profile came back null on all four accounts measured, @nasa and @instagram included. Write your reader to treat missing and null the same way.
What time format are the timestamps in?
Unix seconds, UTC, as integers. taken_at_timestamp on a post measured 1787148707, and created_at on a comment measured 1787782760, so the same unit under a different field name, which is worth knowing when you join posts to their comments. Nothing here is milliseconds and nothing is a formatted string, so multiply by 1000 before handing either to a JavaScript Date.
How do I page through posts and comments?
Two different cursors. For posts and reels, page_info returns next_max_id and end_cursor holding the identical base64 string, and you pass either back as max_id (cursor is accepted as an alias). For comments, next_cursor is a JSON document serialized into a string, measured as {"is_server_cursor_inverse":true,"server_cursor":"AQHT..."}, so pass it back verbatim as cursor without trying to parse it. Stop when has_more is false or next_cursor is null.
Does comment_count match the number of comments I can pull?
comment_count is the total on the post; returned_count is what this call handed you. A measured @nasa post reported comment_count 2994 with returned_count 5 for a limit of 5. Comments come back newest first and paging with cursor walks backwards from there. The two numbers will not converge on a busy post because new comments arrive while you page, so use comment_count as the denominator for a progress bar and not as a termination condition.
A username that does not exist returned TARGET_BLOCKED rather than NOT_FOUND. Is that a bug?
It is expected, and worth designing around. A measured call for a handle that does not exist came back TARGET_BLOCKED with retryable true rather than NOT_FOUND, because the public surface does not distinguish the two clearly enough for us to promise a difference, and telling you an account is gone when it might be a transient miss would be worse. Retry a TARGET_BLOCKED once; if one handle keeps returning it while other handles succeed in the same window, treat it as gone.
What is the Instagram Public Data API?
Instagram Public Data API is a ReefAPI endpoint group for instagram public data It returns live JSON through POST requests under /instagram/v1.
Is the Instagram Public Data API free to try?
Yes. ReefAPI starts with 1,000 free credits, no card required. Instagram Public Data calls use the same shared credit balance as every other ReefAPI engine.
Do I need an Instagram Public Data login or account?
No login to Instagram Public Data is needed for the API response. You call ReefAPI with your x-api-key header, and the playground can run live examples before you create a production key.
How fresh is the Instagram Public Data data?
The page example is captured from a live profile call, and production requests fetch live data through ReefAPI rather than a static sample.
How many credits does the Instagram Public Data API use?
Instagram Public Data actions currently cost 1 credit per successful call. Failed or blocked calls are free, and all APIs draw from one credit pool.
Can I call Instagram Public Data from an AI assistant or MCP client?
Yes. Connect ReefAPI once through MCP and your assistant can call instagram actions with the same key, credit pool and JSON envelope used by normal REST requests.