Meta runs three live Instagram APIs in 2026 and shut the Basic Display API down on 4 December 2024. This guide maps every API to a use case, covers both auth paths and their scopes, gives working curl for publishing and insights, explains the real rate limit formula, and lists what Meta will not release at any access tier.
- Meta runs three live Instagram APIs in 2026 and shut Basic Display down on 4 December 2024.
- The Graph API publishes through graph.facebook.com while Instagram Login serves professional accounts from graph.instagram.com.
- The real rate limit is 4,800 multiplied by the account's impressions over a rolling 24 hours, not a flat 200 calls per hour.
- Publishing a Reel takes three calls because Meta transcodes asynchronously, so you create a container, poll until FINISHED, then publish.
- Stories publishing is limited to Business accounts, and no compliant route to personal accounts exists.
Meta runs three live Instagram APIs in 2026 and killed a fourth. The Instagram Graph API handles publishing, comments, insights and hashtag search for Business and Creator accounts, authenticated through Facebook Login and served from graph.facebook.com. The Instagram API with Instagram Login covers the same professional accounts through a direct Instagram auth path with no Facebook Page required, and it is served from a different host, graph.instagram.com. The Instagram Messaging API is a subset of the Graph API for direct messages. The Basic Display API was shut down on 4 December 2024 and every endpoint returns an error.
The API costs nothing to call. What it costs you is app review, a Business or Creator account requirement, and a rate limit that is not the flat 200 calls per hour most guides quote. The real formula is 4,800 multiplied by your account's impressions over a rolling 24 hours, which means your ceiling moves with your users' reach.
This guide is the full map. Auth paths and scopes, working curl for every core operation including the three-step Reels publish, the rate limit system explained properly, production patterns for webhooks and token refresh, and an honest list of what Meta will not hand over at any access tier. Everything below is current as of July 2026.
Part 1: What is the Instagram API, and which one do I need?
The Instagram API is Meta's set of HTTP endpoints for reading and writing Instagram data on behalf of a professional account that has authorised your app. Which one you need comes down to one question: do you publish and analyse, or do you only read? Everything else follows from that.
Five names circulate and they map to three live products. Getting the name right decides which documentation applies to you, which host you call, and which scopes you request.
| Name | Status | Host | What it does | Auth |
|---|---|---|---|---|
| Instagram Basic Display API | Dead, 4 Dec 2024 | None | Was read-only personal account access | Gone |
| Instagram Graph API | Live | graph.facebook.com | Publishing, comments, mentions, hashtag search, insights, Business Discovery | Facebook Login |
| Instagram API with Facebook Login | Live | graph.facebook.com | Meta's newer name for the same product above | Facebook Login |
| Instagram API with Instagram Login | Live | graph.instagram.com | Professional account access without a linked Facebook Page. Cannot touch ads or tagging | Instagram Login |
| Instagram Messaging API | Live | graph.instagram.com | Send API, templates, conversations. Subset of the above | Instagram Login |
The host difference is the gotcha that costs people an afternoon. The Facebook Login path calls graph.facebook.com. The Instagram Login path calls graph.instagram.com. Copy a curl from the wrong guide and every request 400s with an error that does not mention hostnames.
The December 2024 shutdown, briefly
Meta announced the Basic Display retirement in September 2024 and switched it off on 4 December 2024. No grace period, no partial support. Tokens stopped refreshing and every integration built on it failed at once.
The reason it hurt more than a normal deprecation is that Basic Display was the only official route to a personal Instagram account, and both replacements require a Professional account. For a class of consumer apps there was nothing to migrate to. Day One, the journaling app, had built Instagram import into a paid tier. TechCrunch reported that the replacement APIs only worked for business accounts and did not cover what Day One needed. The feature was removed rather than rebuilt.
I go through the access tiers, the approval path and the rejection patterns in more depth in our guide to Instagram API access.
Which API should you build on?
| What you are building | Which API | Why |
|---|---|---|
| Feed embed or gallery widget | Instagram Login | Read access to profile and media, no Facebook Page to configure |
| Scheduling or publishing tool | Graph API | Content publishing, and Stories publishing is Business-account only |
| Creator analytics dashboard | Graph API | Insights endpoints, audience demographics for the authorised account |
| Comment moderation or community tool | Either | Both paths expose comment management with different scope names |
| DM automation or support inbox | Instagram Login | Send API lives on graph.instagram.com |
| Competitor or hashtag research | Graph API, with limits | Hashtag Search and Business Discovery exist but are capped hard. See Part 6 |
| Anything reading a personal account | None exists | No compliant path since December 2024 |
Part 2: How does authentication work?
OAuth 2.0 authorization code flow on both paths, with an important structural difference. The Facebook Login path makes you hop through a Facebook Page to reach the Instagram account. The Instagram Login path talks to the Instagram account directly.
The flow itself is standard. Your app redirects the user to an authorization URL carrying your App ID and requested scopes. The user approves a consent screen. Instagram redirects back with a short-lived authorization code. Your server exchanges that code plus your App Secret for an access token. Never do that exchange client side, because the App Secret must not leave your backend.
Scopes, by path
The two paths use different scope names for the same capabilities, which is another reason copied code fails. Request the minimum set your core feature needs. Over-requesting is one of the most common app review rejections and it is free to avoid.
| Facebook Login path | Instagram Login path |
|---|---|
| instagram_basic | instagram_business_basic |
| instagram_content_publish | instagram_business_content_publish |
| instagram_manage_comments | instagram_business_manage_comments |
| instagram_manage_insights | No direct equivalent, insights are narrower |
| pages_show_list, pages_read_engagement | Not required, no Page involved |
| No messaging equivalent | instagram_business_manage_messages |
Token types and lifetimes
| Token | Lifetime | Use it for |
|---|---|---|
| Authorization code | Minutes | One server-side exchange, then discard |
| Short-lived user token | About 1 hour | Testing in the Graph API Explorer |
| Long-lived user token | About 60 days | Production user connections. Refreshable after 24 hours |
| Page access token | Inherits the user token | Acting on behalf of the Page linked to the Instagram account |
| System user token | Does not expire | Server-to-server automation you own. Business Manager only |
The 60-day expiry is where integrations die quietly. There is no single outage to alert on. Connections fail one at a time as each user reaches their own expiry, and the support tickets arrive before your monitoring does. Refresh at day 50 to 55, and alert on refresh failures rather than on request failures.
An App ID and App Secret are not an access token. The App ID identifies your app and does not expire on a schedule. An access token is tied to one user, expires, and only exists after that user approves your app. Most integrations need both.
Part 3: Working code for every core operation
Everything below is curl against the Facebook Login path unless noted. Swap the host to graph.instagram.com and the scope names per the table above for the Instagram Login path. Replace the version string with the current one, which was v25.0 as of early 2026.
Step 1: find the Page token and the Instagram user ID
On the Facebook Login path you cannot call Instagram endpoints with a plain user token. You first list the Pages the user manages, then pull the Page token and the linked Instagram account ID out of that response.
curl -s -X GET \
"https://graph.facebook.com/v25.0/me/accounts\
?fields=name,access_token,tasks,instagram_business_account\
&access_token=USER_ACCESS_TOKEN"
# Response
{
"data": [
{
"name": "Your Page",
"access_token": "PAGE_ACCESS_TOKEN",
"instagram_business_account": { "id": "IG_USER_ID" },
"id": "PAGE_ID",
"tasks": ["ANALYZE","ADVERTISE","MESSAGING","MODERATE",
"CREATE_CONTENT","MANAGE"]
}
]
}Keep PAGE_ACCESS_TOKEN and IG_USER_ID. Almost every call below uses one or both. If you already know the Page ID, query it directly and skip the list:
curl -s -X GET \
"https://graph.facebook.com/v25.0/PAGE_ID\
?fields=name,access_token,instagram_business_account\
&access_token=USER_ACCESS_TOKEN"Step 2: read the profile and the media list
The Graph API is a node and edge model. Nodes are things: a profile, a photo, a comment. Edges are the relationships between them, so the media edge on a user node returns that user's posts. You request exactly the fields you want and nothing else comes back.
# Profile
curl -s -X GET \
"https://graph.facebook.com/v25.0/IG_USER_ID\
?fields=id,username,followers_count,media_count\
&access_token=PAGE_ACCESS_TOKEN"
# Media, newest first, 25 per page
curl -s -X GET \
"https://graph.facebook.com/v25.0/IG_USER_ID/media\
?fields=id,caption,media_type,media_url,permalink,\
like_count,comments_count,timestamp\
&limit=25&access_token=PAGE_ACCESS_TOKEN"Two behaviours to design around. Ordering is not supported, so you cannot ask the API to sort. And every endpoint uses cursor-based pagination except the User Insights edge, which is the only one that supports time-based pagination. Do not write one pagination helper and assume it covers everything.
Step 3: publish a Reel, the three-step container flow
Publishing is never a single call. You create a container, poll it until Meta finishes transcoding, then publish the container. Teams that miss the polling step ship code that works on small test files and fails on real ones.
3a. Create the container.
curl -s -X POST \
"https://graph.facebook.com/v25.0/IG_USER_ID/media\
?media_type=REELS\
&video_url=https://cdn.example.com/reel.mp4\
&caption=Hello%20World\
&share_to_feed=false\
&access_token=PAGE_ACCESS_TOKEN"
# Response
{ "id": "18270815569115548" } # this is IG_CONTAINER_IDThe video_url must be publicly reachable, because Meta pulls the file from your server rather than accepting an upload. share_to_feed set to true puts the Reel in both the Feed and the Reels tab. False keeps it in Reels only.
3b. Poll the container until it is ready.
curl -s -X GET \
"https://graph.facebook.com/v25.0/IG_CONTAINER_ID\
?fields=status_code,status\
&access_token=PAGE_ACCESS_TOKEN"
# Response while transcoding
{ "status_code": "IN_PROGRESS", "status": "Media is being processed." }
# Response when ready
{ "status_code": "FINISHED",
"status": "Finished: Media has been uploaded and it is ready to be published.",
"id": "18270815569115548" }3c. Publish it.
curl -s -X POST \
"https://graph.facebook.com/v25.0/IG_USER_ID/media_publish\
?creation_id=IG_CONTAINER_ID\
&access_token=PAGE_ACCESS_TOKEN"
# Response
{ "id": "90011803596441" } # IG Media IDPoll on a backoff rather than a tight loop, because every poll consumes quota from the same budget the publish call needs. Treat any status_code other than FINISHED as not ready, and surface ERROR states to the user rather than retrying blindly.
Reels media specifications
Uploads that violate these fail at the container stage with messages that are not always specific. Validate before you call.
| Property | Requirement |
|---|---|
| Container format | MOV or MP4, MPEG-4 Part 14 |
| Video codec | HEVC or H.264 |
| Audio codec | AAC, 48 kHz |
| Frame rate | 23 to 60 FPS |
| Picture size | Maximum 1920 horizontal pixels, 9:16 recommended |
| Video bitrate | 25 Mbps maximum |
| Audio bitrate | 128 kbps |
| Duration | 3 seconds minimum, 15 minutes maximum |
| File size | 1 GB maximum |
One rule that catches product teams rather than engineers: content publishing is available to all Instagram Professional accounts, except Stories, which are Business accounts only. If your product promises Stories scheduling to Creator accounts, that promise cannot be kept.
Step 4: comments
# Read comments on a post
curl -s -X GET \
"https://graph.facebook.com/v25.0/MEDIA_ID/comments\
?fields=id,text,username,timestamp,like_count\
&access_token=PAGE_ACCESS_TOKEN"
# Reply to a comment
curl -s -X POST \
"https://graph.facebook.com/v25.0/COMMENT_ID/replies\
?message=Thanks%20for%20this\
&access_token=PAGE_ACCESS_TOKEN"
# Hide a comment
curl -s -X POST \
"https://graph.facebook.com/v25.0/COMMENT_ID\
?hide=true&access_token=PAGE_ACCESS_TOKEN"Do not poll for new comments. Subscribe a webhook instead. Polling every minute across a few hundred accounts will exhaust quota that your publishing depends on, for reasons Part 4 makes concrete.
Step 5: insights
# Account level
curl -s -X GET \
"https://graph.facebook.com/v25.0/IG_USER_ID/insights\
?metric=reach,impressions,profile_views,follower_count\
&period=day&access_token=PAGE_ACCESS_TOKEN"
# Post level
curl -s -X GET \
"https://graph.facebook.com/v25.0/MEDIA_ID/insights\
?metric=impressions,reach,saved,engagement\
&access_token=PAGE_ACCESS_TOKEN"Insights metrics have changed twice in the last eighteen months and Meta deprecates them on short notice. Check the changelog before you assume a field still exists, and design your dashboard to degrade gracefully when a metric returns nothing rather than throwing.
Step 6: send a direct message
Messaging lives on the Instagram Login path and therefore on a different host. Conversations only start when the Instagram user messages your app user first, and you can only reply inside a 24-hour window after their last message.
curl -s -X POST \
"https://graph.instagram.com/v25.0/IG_USER_ID/messages" \
-H "Authorization: Bearer IG_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"recipient": { "id": "IG_SCOPED_ID" },
"message": {
"attachment": {
"type": "template",
"payload": {
"template_type": "button",
"text": "What do you want to do next?",
"buttons": [
{ "type": "web_url",
"url": "https://example.com",
"title": "Visit site" }
]
}
}
}
}'Four messaging constraints worth knowing before you scope a support inbox. Group messaging is not supported, so one conversation means one customer. Your app user must own any media referenced in a message. Messages sitting in the Requests folder with no activity for 30 days stop being returned by the API. And when a customer shares a post, the webhook payload carries only the URL, not the content.
Part 4: The rate limit system, properly explained
Instagram does not run one rate limiter. It runs two, and which applies depends on the endpoint. Almost every guide reports a flat 200 calls per hour, and for the main Instagram Platform endpoints that number is not the mechanism. The real formula, from Meta's own rate limiting documentation, is:
Calls within 24 hours = 4800 × Number of ImpressionsImpressions here means the number of times any content from the connected professional account entered a person's screen in the last 24 hours. The count is tracked per app and account pair, in a rolling 24-hour window, and it moves as that account's reach moves.
Work the arithmetic, because it is the whole point. An account with 1,000 impressions yesterday gives you 4,800,000 calls today. An account with 10 impressions gives you 48,000. A widely shared guide currently states that ten impressions yields 48 calls, which is out by a factor of a thousand, and I mention it only because capacity plans get built on numbers like that.
So the honest summary is not that small accounts get a tiny allowance in absolute terms. It is that the ceiling is invisible and it moves. You cannot read the impression count through the API. You only get a percentage in a response header, so you are managing against a limit you cannot see, on an account whose reach changes daily.
Two limiters, and which endpoints use which
| Limiter | Applies to |
|---|---|
| Business Use Case, 4800 × impressions | Media, insights, comments, mentions, content publishing. Effectively the whole Instagram Platform surface |
| Platform Rate Limits | Business Discovery and Hashtag Search. A separate system with separate ceilings |
| Messaging limits | Counted per professional account and per API. Overlaps with BUC, so a DM consumes both |
| Publishing cap | Around 25 API-published feed posts per 24 hours per account, independent of everything above |
Two consequences that catch teams. First, all Instagram Platform endpoints for a given app and account pair share one BUC pool, so an aggressive insights poller will quietly eat the budget your publishing needs. Second, Advanced Access does not change the formula. Teams hitting throttling often apply for higher access expecting relief, wait weeks, and get none.
Read the usage header
Every response carries X-Business-Use-Case-Usage. Parse it on every response, not just on failures, and build it into your HTTP client so nobody has to remember.
"X-Business-Use-Case-Usage": {
"IG_USER_ID": [
{
"type": "INSTAGRAM",
"call_count": 28, // percent of quota used, not raw calls
"total_cputime": 10, // percent
"total_time": 20, // percent
"estimated_time_to_regain_access": 0 // minutes, when throttled
}
]
}call_count is a percentage, not a raw count. Throttle yourself at 80 rather than waiting for the block. When you are throttled, estimated_time_to_regain_access tells you how long, so use it instead of guessing a backoff interval. Error code 80002 is the one that means quota exhausted.
There are also CPU and time budgets alongside the call budget, calculated as 720,000 multiplied by impressions for total CPU time and 2,880,000 multiplied by impressions for total time. An expensive query can exhaust those before your call count gets close.
A trap worth naming: your development accounts lie to you. Test accounts usually belong to team members with normal Instagram activity, which gives a comfortable quota that your real customers, especially new creators, will not have. We wrote up the specific error patterns in our guide to Instagram API rate limit errors.
Part 5: Production patterns
Four patterns separate an integration that survives a year from one that needs a rewrite. None of them are Instagram-specific, but Instagram punishes their absence harder than most APIs.
Webhooks instead of polling
Meta offers no firehose. If you want near real-time updates on comments, mentions or messages, you subscribe webhooks at the app level and then re-subscribe per user. Polling is the fastest way to burn a BUC budget for no benefit. Configure a public endpoint that accepts POST, subscribe only to the topics you use, and validate the signature on every payload, because an unvalidated webhook endpoint is an open door.
Batch requests
Fetching comment counts for twenty posts as twenty calls is twenty calls against your quota. Batching bundles independent calls into one HTTP request and one response, which cuts round trips and quota consumption together.
A token refresh job, not a token refresh function
# Refresh a long-lived token, Facebook Login path
curl -s -X GET \
"https://graph.facebook.com/v25.0/oauth/access_token\
?grant_type=fb_exchange_token\
&client_id=APP_ID\
&client_secret=APP_SECRET\
&fb_exchange_token=CURRENT_LONG_LIVED_TOKEN"
# Run this on a schedule at day 50-55, per connected account.
# Alert on refresh failures, not on request failures.
# A token can also die early if the user changes their password
# or revokes the app, so check validity at startup too.An error taxonomy, not a retry loop
Sort failures into retry and do-not-retry before you write any retry code, because retrying the wrong class of error makes throttling worse rather than better.
| Class | Examples | Handling |
|---|---|---|
| Retry with backoff | 429, 80002 quota exhausted, 5xx, network timeouts | Exponential backoff with jitter. Use estimated_time_to_regain_access when present |
| Do not retry, fix first | Missing scope, malformed query, unsupported media, invalid caption | Surface the error. The same payload will fail forever |
| Do not retry, re-auth | Expired or revoked token, user removed the app | Prompt the user to reconnect. Mark the connection dead in your UI |
| Do not retry, wait | Container not FINISHED, media still transcoding | Poll the status endpoint on a backoff |
Part 6: What Meta will not give you, at any tier
Six things. Four of them are not difficulty, they are Meta deciding the data belongs to the account holder, which means no access tier and no approval will move them.
| What you cannot get | Why | Closest available |
|---|---|---|
| Audience demographics for a creator who has not authorised your app | Released to the account owner only | Demographics for creators who connect through your product |
| Any personal account data | No endpoint since December 2024 | Ask the user to convert to Professional |
| Follower and following lists | Not exposed on the Graph API | Follower counts only |
| Historical Stories metrics | Stories expire in 24 hours and are not archived | Live polling while the Story is up |
| Earnings or monetisation data | Never exposed on any Instagram surface | Consented income signals across platforms |
| Deep competitive research | Hashtag Search and Business Discovery are capped per account per week and return top media only | A purpose-built listening tool |
That last row is the one product managers argue with. Hashtag Search and Business Discovery genuinely exist, and they are genuinely not a competitive intelligence product. If listening is the requirement, our write-up on Instagram social listening covers what that actually takes.
Part 7: When should you use a unified layer instead?
When you need Instagram alongside other platforms, or when the fields you need only exist behind the creator's own login. If Instagram is your only platform and you have an engineer to spend, build direct. Meta's API is free and the docs are good.
The case for a layer is not that Instagram is hard on its own. It is that every section of this guide repeats per platform, differently. TikTok has its own audit, a 24-hour token expiry and no webhooks. LinkedIn approval runs months. YouTube uses quota units where a search call costs a hundred times a metadata call. Four platforms is not four times the work, because normalisation grows faster than integration count. I compared all four in which platform API to integrate first.
That is what Phyllo's Instagram API removes. A creator connects through your product, we handle OAuth, the app review burden, token refresh, quota distribution and the polling, and you get normalised fields through the same schema serving 25+ platforms. The API reference is public if you would rather read endpoints than talk to anyone. Costs are in our Instagram API pricing breakdown.
The honest limit. We need the creator to connect. Someone who has not authorised anything does not appear in your data, through us or through Meta. If cold discovery is your workflow, that is a different category of tool and we say so in consent-based versus public social APIs.
Is the Instagram API free?
Meta charges no per call fee on any Instagram API. The cost is app review, business verification, a Professional account and roughly four to eight weeks of engineering.
What is the Instagram API rate limit?
For Instagram Platform endpoints it is 4,800 multiplied by the account's impressions over a rolling 24 hours. The flat 200 calls per hour figure is a legacy rule of thumb.
Is the Instagram Basic Display API still available?
No. Meta shut it down on 4 December 2024 and every endpoint returns an error. Use the Instagram API with Instagram Login for lightweight profile and media access.
Can I access a personal Instagram account through an API?
Not since December 2024. Both live APIs require a Professional account, meaning Business or Creator. Converting is free. Tools claiming otherwise breach Meta's terms.
Do I need a Facebook Page to use the Instagram API?
For the Graph API path, yes, and the account must be linked to a Page. The Instagram API with Instagram Login needs no Page, so check whether that path covers you first.
Why does publishing take three API calls?
Meta transcodes media asynchronously. You create a container, poll its status_code until it reads FINISHED, then publish by creation_id. Skipping the poll fails on real uploads.
Can I schedule Instagram Stories through the API?
Only for Business accounts. Content publishing covers all Professional accounts except Stories, which are Business only, so Creator account Stories cannot be scheduled.
What is the difference between an Instagram API key and an access token?
The App ID and App Secret identify your app and do not expire on a schedule. An access token is tied to one user, expires within 60 days, and exists only after OAuth approval.

.png)