Instagram API in Python: profiles, posts and comments (2026)
Instagram's official API serves professional accounts you own or manage. For public data on any account, four endpoints in this order: Instagram account profile, Instagram account posts, Instagram post comments and Instagram reel transcript. The worked example is 8 calls and costs at most $0.032; a failed call is not charged.
Updated
Instagram does have an official API, and it is the right tool for one kind of job. Meta's Instagram Platform overview describes it as a set of APIs for Instagram professional accounts, meaning business and creator accounts, that you own or manage, or whose owners log in to your app. It covers publishing, comment moderation, messaging and insights. For other people's accounts it offers Business Discovery, which returns basic metadata and metrics about other professional accounts, and nothing for age-gated ones. If your job is your own account, use Meta's API.
This guide is for the other job: reading public data on any public account, a competitor, a creator you might hire, a brand you study, from Python with nothing but requests. You end with one JSON file per account: the profile, the recent posts with their counts, the comments on the posts that drew the most discussion, and what is said in the most played reels, with timestamps.
No Instagram login, Meta app or access token is involved. Each call is an HTTPS request with your key.
Step by step
Step 1: the profile
Instagram account profile takes username, with or without @, and returns one account for $0.004: id (the numeric account ID), username, name, bio, followers, following, posts, verified and avatar_url. With view=full you also get ext.private, ext.website, ext.links (the links in the bio), ext.category and ext.business. likes is null: Instagram shows no account-wide like total.
curl -G "https://api.okdata.online/v1/instagram/users/get" \
-H "Authorization: Bearer $OKDATA_API_KEY" \
-d username=northfieldbakery -d view=full
Check ext.private before going further. A private account's profile comes back, but its posts are not public.
Step 2: the posts
Instagram account posts takes username and type: all (the default, every post) or reel (reels only, from the account's reels tab). Posts come newest first, 12 a page, for $0.004 a page. Each post has:
id: the shortcode, the part after/p/or/reel/in a link, such asDQx7pLk2mVe. The comment and transcript endpoints take it.type:videofor reels and videos,imagefor photos and carousels.text(the caption),hashtags,images,published_atandurl.metrics.likes,metrics.commentsand, for videos,metrics.views(plays).
With view=full, ext.duration_s, ext.coauthors (the other authors of a collab post), ext.product_type and ext.media_id are added. Send page.next_cursor back as cursor for the next page and stop when page.has_more is false.
curl -G "https://api.okdata.online/v1/instagram/users/posts/list" \
-H "Authorization: Bearer $OKDATA_API_KEY" \
-d username=northfieldbakery -d type=all -d view=full
Step 3: the comments
Instagram post comments takes the shortcode as id, or the post's link as url. Comments come newest first, about 15 a page, for $0.004 a page. Each has id, text, author.username, likes, replies (how many replies sit under it) and published_at. author.name is null. To read the replies under one comment, pass its id and the post's shortcode as post_id to Instagram comment replies.
curl -G "https://api.okdata.online/v1/instagram/posts/comments/list" \
-H "Authorization: Bearer $OKDATA_API_KEY" \
-d id=DQx7pLk2mVe
Step 4: what a reel says
Instagram reel transcript takes a shortcode or link and returns what is said in a reel or video post, for $0.004: segments with start and end in seconds, and the whole text. It handles videos up to two minutes long. lang comes back as und (not identified), so detect the language yourself if it matters. A video with no speech returns not_found, which is not charged.
curl -G "https://api.okdata.online/v1/instagram/posts/transcript/get" \
-H "Authorization: Bearer $OKDATA_API_KEY" \
-d id=DRb3nVw8kQz
Step 5: the whole script
The script makes at most eight calls: one profile, up to three post pages, two comment pages and two transcripts. Each logical request gets its own Idempotency-Key, so a retry after a dropped connection is never billed twice. rate_limited waits for retry_after_seconds; any other error stops with the code and message.
import json
import os
import time
import uuid
import requests
API = "https://api.okdata.online"
KEY = os.environ["OKDATA_API_KEY"]
USERNAME = "northfieldbakery"
POST_PAGES = 3 # 12 posts a page; each page is one call
COMMENT_POSTS = 2 # first comment page of the most commented posts
TRANSCRIPTS = 2 # the most played videos of two minutes or less
class ApiError(Exception):
def __init__(self, body):
super().__init__(f"{body['code']}: {body['message']} (charged: {body['charged']})")
self.code = body["code"]
def call(path, **params):
headers = {"Authorization": f"Bearer {KEY}", "Idempotency-Key": str(uuid.uuid4())}
for attempt in range(3):
try:
body = requests.get(API + path, headers=headers, params=params, timeout=90).json()
except requests.RequestException:
time.sleep(2 ** attempt) # same key on the retry: no double charge
continue
if body["code"] == "ok":
return body
if body["code"] == "rate_limited":
time.sleep(body.get("retry_after_seconds") or 2)
continue
raise ApiError(body)
raise RuntimeError(f"{path}: no answer after 3 attempts")
profile = call("/v1/instagram/users/get", username=USERNAME, view="full")["data"]["item"]
if profile["ext"]["private"]:
raise SystemExit(f"@{USERNAME} is private; its posts are not public")
posts, cursor = [], None
for _ in range(POST_PAGES):
body = call("/v1/instagram/users/posts/list", username=USERNAME, view="full", **({"cursor": cursor} if cursor else {}))
posts += body["data"]["items"]
if not body["page"]["has_more"]:
break
cursor = body["page"]["next_cursor"]
for post in sorted(posts, key=lambda p: p["metrics"]["comments"] or 0, reverse=True)[:COMMENT_POSTS]:
post["comment_page"] = call("/v1/instagram/posts/comments/list", id=post["id"])["data"]["items"]
videos = [p for p in posts if p["type"] == "video" and (p["ext"]["duration_s"] or 0) <= 120]
for post in sorted(videos, key=lambda p: p["metrics"]["views"] or 0, reverse=True)[:TRANSCRIPTS]:
try:
post["transcript"] = call("/v1/instagram/posts/transcript/get", id=post["id"])["data"]["item"]
except ApiError as err:
if err.code != "not_found": # no speech in the video: not charged
raise
with open(f"{USERNAME}.json", "w", encoding="utf-8") as f:
json.dump({"profile": profile, "posts": posts}, f, ensure_ascii=False, indent=2)
print(profile["followers"], "followers;", len(posts), "posts saved")
Raise POST_PAGES to go further back: ten pages is about 120 posts and costs $0.04 for the posts alone.
Good to know
- Shortcodes are the post IDs. Every post endpoint takes the shortcode as
id, or the full link asurl.ext.media_idholds Instagram's numeric media ID if another tool needs it. - Comments are newest first. There is no "most liked" order. To find the comments that resonated, read a few pages and sort by
likesyourself. - What is null. Posts have no
title, and shares, saves and reposts are null because Instagram does not show them. Plays exist only for videos. - Reels only.
type=reellists the reels tab. When video is what you study, it saves the pages you would otherwise spend on photos. - Failed calls cost nothing, and so do
not_foundanswers for deleted posts and videos without speech. - Other accounts on the official API. Business Discovery covers basic metadata and metrics of other professional accounts. When that is enough and you already run a Meta app, it is a fine option.
To weigh Meta's API against a third-party API for your project, see official API vs third-party social data. Watching a few accounts over time is covered in competitor monitoring.
What the example costs
One account's profile, its last three pages of posts, the first comment page of its two most commented posts and the transcripts of its two most played reels
| Endpoint | Calls | Price | Subtotal |
|---|---|---|---|
| Instagram account profile | 1 | $0.004 | $0.004 |
| Instagram account posts | 3 | $0.004 | $0.012 |
| Instagram post comments | 2 | $0.004 | $0.008 |
| Instagram reel transcript | 2 | $0.004 | $0.008 |
| Total | 8 | $0.032 |
Prices in force when this page was built. A failed call costs nothing.
In your AI (MCP)
Connect the MCP server once, then say what you want. The agent finds these endpoints, reads their prices and calls them on your balance.
SayLook up the Instagram account northfieldbakery: followers and bio, its last 36 posts with likes, comments and plays, the first page of comments on its two most commented posts, and what is said in its two most played reels. Summarise what its audience asks about most.
What the agent sends to the call tool first
{
"endpoint": "instagram.users.get",
"params": {
"username": "nasa"
},
"idempotency_key": "<a new UUID>"
}