InstagramAnswer

Instagram Basic Display API deprecated: replacement and personal accounts

Meta shut the Basic Display API on Dec 4, 2024. The replacement needs a professional account. What it can't read, and a script for public profiles.

8 min read

Short answer

  • Meta ended the Instagram Basic Display API on December 4, 2024; its changelog says every request returns an error. Its replacements, Instagram API with Instagram Login and with Facebook Login, work only for Business and Creator accounts.
  • No official API reads a personal account now. Business Discovery reads other accounts, but only Business and Creator ones, is documented only for Facebook Login, and returns no data for age-gated accounts.
  • ToolzerHub reads public profiles and posts by username: 1 credit per call, up to 50 posts per call, $0.99 to $1.90 per 1,000 credits, 100 free credits a month. It has no login flow and no write access.
On this page

Meta shut down the Instagram Basic Display API on December 4, 2024, and nothing replaces it for personal accounts. The two APIs that remain work only for Instagram Business and Creator accounts. We read Meta's documentation on 2026-10-16.

What Meta says about the shutdown

Meta's changelog entry for December 4, 2024 reads: "The Instagram Basic Display API has been deprecated. All requests to the Instagram Basic Display API will return an error message." The entry applies to all versions (changelog updated October 5, 2026).

Meta's announcement of September 4, 2024 gave 90 days' notice and said the API was "used to connect Instagram accounts to third-party consumer apps". It ends with the line that matters for personal accounts: "After December 4th, 2024, there will no longer be a set of Instagram APIs for consumer developer apps." Meta's old Basic Display URL now redirects to its Instagram Platform page (/documentation/instagram-platform).

The replacement works only for professional accounts

Meta points Basic Display developers to the Instagram API with Instagram Login or the Instagram API with Facebook Login. The overview page (updated September 28, 2026) says: "To use the APIs, your app users must have an Instagram professional account. An Instagram professional account can be for a business or creator."

Instagram Login Facebook Login
Facebook Page Not needed Required
Host graph.instagram.com graph.facebook.com
Token Instagram User Facebook User or Page
Hashtag search No Yes
Business Discovery Not documented Yes
Publishing, insights Yes Yes

Serving accounts you don't own needs Advanced Access, which requires App Review and Business Verification. We found no Meta page that lets a personal account connect to either API.

What Business Discovery can read about other accounts

Business Discovery is the official way to read another account's profile and post metrics. (oEmbed returns embed HTML for a single public post, and Meta's terms bar using it for analytics.) Meta's reference says it is "Available for the Instagram API with Facebook Login" and returns data about "another Instagram Business or Creator" account. Data about age-gated accounts is not returned.

You call it on your own professional account's ID (Meta's sample uses 17841405309211844) with the other account's username: GET /17841405309211844?fields=business_discovery.username(bluebottle){followers_count,media_count,media}. The public fields include biography, followers_count, media_count, username and website, and the media edge can expand to comments_count, like_count and view_count. Meta notes that view_count includes paid and organic views, and that a direct request for any returned media object fails with insufficient permissions.

The token needs instagram_basic, instagram_manage_insights and pages_read_engagement (plus ads_read or ads_management if your Page role came through Business Manager). Business Discovery falls under Graph API Platform Rate Limits and uses a user token, and Meta does not publish the call count for user tokens. The media edge pages with before and after cursors only.

What you can do instead for public accounts

We make ToolzerHub. Our Instagram API reads a public profile or its posts from a username, with one API key and no Meta app, no Facebook Page and no professional account of your own. This script writes profiles and recent posts to two CSV files. To page an account's whole posts list, see Instagram API get user posts.

python
import csv
import os
import sys
import time
from datetime import datetime, timezone

import requests

BASE = "https://api.toolzerhub.com/v1/instagram"
HEADERS = {"x-api-key": os.environ["TOOLZERHUB_API_KEY"]}
PROFILE_FIELDS = ["username", "user_id", "full_name", "verified", "private", "followers",
                  "following", "external_url", "biography"]
POST_FIELDS = ["username", "code", "posted", "type", "likes", "comments", "caption", "url"]


def cell(value):
    # Spreadsheets run text that starts with = + - or @ as a formula; an apostrophe keeps it text.
    if isinstance(value, str) and value[:1] in ("=", "+", "-", "@", "\t", "\r"):
        return "'" + value
    return value


def get(path, params, tries=4):
    # 429 and 503 are retryable.
    for attempt in range(tries):
        r = requests.get(f"{BASE}/{path}", headers=HEADERS, params=params, timeout=90)
        if r.status_code not in (429, 503) or attempt == tries - 1:
            r.raise_for_status()
            return r.json()["data"]
        time.sleep(5 * (attempt + 1))


def profile_row(p):
    return {
        "username": p["username"],
        "user_id": p["id"],
        "full_name": p.get("full_name"),
        "verified": p.get("is_verified"),
        "private": p.get("is_private"),
        "followers": (p.get("edge_followed_by") or {}).get("count"),
        "following": (p.get("edge_follow") or {}).get("count"),
        "external_url": p.get("external_url"),
        "biography": (p.get("biography") or "").replace("\n", " "),
    }


def post_row(username, m):
    return {
        "username": username,
        "code": m["code"],
        "posted": datetime.fromtimestamp(m["taken_at"], timezone.utc).isoformat(timespec="minutes"),
        "type": m.get("product_type"),
        "likes": m.get("like_count"),
        "comments": m.get("comment_count"),
        "caption": ((m.get("caption") or {}).get("text") or "").replace("\n", " "),
        "url": f"https://www.instagram.com/p/{m['code']}/",
    }


def main(usernames, limit="24"):
    limit = int(limit)
    n_profiles = n_posts = n_calls = 0
    with open("profiles.csv", "w", newline="", encoding="utf-8") as pf, \
         open("posts.csv", "w", newline="", encoding="utf-8") as qf:
        profiles = csv.DictWriter(pf, fieldnames=PROFILE_FIELDS)
        posts = csv.DictWriter(qf, fieldnames=POST_FIELDS)
        profiles.writeheader()
        posts.writeheader()
        for name in [u.strip() for u in usernames.split(",") if u.strip()]:
            profile = get("user/info", {"username_or_url": name})
            n_calls += 1
            if not profile:  # unknown usernames return 200 with data null
                print(f"{name}: not found")
                continue
            n_profiles += 1
            profiles.writerow({k: cell(v) for k, v in profile_row(profile).items()})
            cursor, got = None, 0
            while got < limit:
                params = {"user_id": profile["id"], "count": min(50, limit - got)}
                if cursor:
                    params["cursor"] = cursor
                page = get("user/posts", params)
                n_calls += 1
                for m in page["items"][: limit - got]:
                    posts.writerow({k: cell(v) for k, v in post_row(name, m).items()})
                    got += 1
                cursor = page.get("next_max_id")
                if not page.get("more_available") or not page["items"]:
                    break
            n_posts += got
    print(f"wrote {n_profiles} profiles and {n_posts} posts, {n_calls} calls")


if __name__ == "__main__":
    if len(sys.argv) < 2:
        sys.exit("usage: python instagram_profile_posts.py user1,user2 [posts_per_user]")
    main(*sys.argv[1:])

/v1/instagram/user/info takes a username and returns the profile. /v1/instagram/user/posts returns up to 50 posts per call and a next_max_id that you pass back as cursor for the next page. cell() puts an apostrophe in front of text that starts with =, +, - or @, so a spreadsheet shows it as text.

Run the script

bash
export TOOLZERHUB_API_KEY=your_key
python instagram_profile_posts.py nasa,mrbeast,natgeo 60

Output on 2026-10-16: wrote 3 profiles and 180 posts, 9 calls, in 38.8 seconds. Each account needed one profile call and two post calls (50, then 10). The profile file:

username followers following verified
nasa 104,375,026 90 yes
mrbeast 90,969,166 1,013 yes
natgeo 268,361,712 195 yes

The first posts of the NASA file:

posted type likes comments
2026-10-10 clips 688,608 6,824
2026-10-06 feed 257,196 790
2026-10-02 carousel_container 1,240,275 4,892

Notes on the 180 rows:

  • Median likes were about 170,300 for nasa, 879,200 for mrbeast and 57,000 for natgeo.
  • In all three accounts the first three rows are older than the rest. Pinned posts are the likely cause, which we did not confirm, so sort by posted if you need newest first. Without those rows, dates ran from 2026-03-05 to 2026-10-09 for mrbeast and 2026-09-29 to 2026-10-16 for natgeo.
  • type is Instagram's own label: clips is a reel, feed a single photo or video, carousel_container a multi-image post.
  • No post had a view count in the list. Reel views come from /v1/instagram/post/views at 1 credit per reel. On 2026-10-16 it returned 23,543,099 video_view_count and 61,516,871 play_count for one mrbeast reel. They are different metrics: video_view_count is the embed view count, and play_count is the plays shown on the owner's Reels tab, available only for their 36 newest reels.

The request carries only a username and works the same for any public account; the response has no account-type field. We tested public accounts only.

Where Meta's API is the better pick

Use Meta's API when the account is yours or your customers' and they can sign in: reach and impressions, publishing, comment moderation and messaging exist only there. Our API reads what a signed-out visitor sees, with no login flow and no write access.

Smaller gaps in what we returned on 2026-10-16:

  • detail=full on /v1/instagram/user/info came back with is_partial: true and full_profile_unavailable for both nasa and mrbeast, so there was no media_count. Business Discovery returns media_count.
  • Posts carry like_count and comment_count, but no insights fields.
  • We return public data only and did not test private accounts.
  • In the 7 days to 2026-10-16, /v1/instagram/user/info returned 503 on 4.6% of 13,794 calls; the script above retries 503 and 429. /v1/instagram/user/posts had 35 calls in that window, so we ran 10 more live accounts and all 10 returned posts.

What it costs

Each call is 1 credit. The run above used 9 credits: 1 to 2 cents at our rates of $0.99 to $1.90 per 1,000 credits ($1.45 on the $29 plan). At 50 posts per call, 1,000 posts from one account is 20 post calls plus 1 profile call: 21 credits, or 2 to 4 cents. The free tier gives 100 credits a month. For comments on a post, see how to export Instagram comments; for comments and keyword search in one script, see our Instagram scraper in Python.

We are not affiliated with Meta Platforms, Inc. or Instagram.

Sources

Get this data from your own code

The Instagram API returns it as JSON with one x-api-key header. 100 free credits monthly.