For AI

How-to guides

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 as DQx7pLk2mVe. The comment and transcript endpoints take it.
  • type: video for reels and videos, image for photos and carousels.
  • text (the caption), hashtags, images, published_at and url.
  • metrics.likes, metrics.comments and, 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 as url. ext.media_id holds 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 likes yourself.
  • 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=reel lists 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_found answers 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

EndpointCallsPriceSubtotal
Instagram account profile1$0.004$0.004
Instagram account posts3$0.004$0.012
Instagram post comments2$0.004$0.008
Instagram reel transcript2$0.004$0.008
Total8$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>"
}

Set up the MCP server →

Sources

  1. Instagram Platform overviewdevelopers.facebook.com · read
  2. Instagram API with Facebook Login: Business Discoverydevelopers.facebook.com · read

Connect once, then just ask.

Every new key comes with $0.10 to try it. One sentence connects your agent.

Start free
Connect your AI

Paste this into your agent

Read https://okdata.online/agents.md and set up okdata for me; my key is in the OKDATA_API_KEY environment variable.

Claude Code, Cursor, Codex and other agents that can open a link set themselves up from it.

MCP serverhttps://api.okdata.online/mcp