What is the Instagram API?
“Instagram API” is the name most people use for what Meta now calls the Instagram Platform: a set of Graph API endpoints that work on Instagram professional accounts. Developers use it to publish content, read post and account insights, reply to and hide comments, handle mentions and send messages. Everything runs over HTTPS with an access token, and the current Graph API version is v26.0, released on July 29, 2026 (Meta changelog). The examples on this page use it.
Three things trip people up before they write a line of code:
- Only Business and Creator accounts. Meta’s docs say your app users “must have an Instagram professional account.” Personal accounts cannot be read or posted to.
- The Basic Display API is gone. Meta’s announcement set December 4, 2024 as the end date, and every request since returns an error. Tutorials that use
user_profileoruser_mediascopes are dead. - There are two versions of the API. One uses Instagram Login and one uses Facebook Login for Business. They share most endpoints but differ in hosts, permissions and a few features. Picking the wrong one is the most common reason people rebuild their auth later.
Instagram Login or Facebook Login: which API should you use?
Here is the comparison from Meta’s overview, reduced to what changes your build:
| Instagram API with Instagram Login | Instagram API with Facebook Login | |
|---|---|---|
| User logs in with | Instagram credentials | Facebook credentials |
| Facebook Page needed | No | Yes, linked to the Instagram account |
| Host | graph.instagram.com | graph.facebook.com |
| Token | Instagram User token | Facebook User or Page token |
| Publishing permissions | instagram_business_basic, instagram_business_content_publish | instagram_basic, instagram_content_publish, pages_read_engagement |
| Publishing, insights, comments, mentions, messaging | Yes | Yes (messaging via the Messenger Platform) |
| Hashtag search | No | Yes |
| Product tagging, partnership ads | No | Yes |
Source: Instagram Platform overview and content publishing. Read 28 September 2026.
Rule of thumb: if you publish content, read your own insights and handle comments or DMs, use Instagram Login. Your users do not need a Facebook Page, which removes the biggest source of onboarding failures. Choose Facebook Login when you need hashtag search, product tags or ads features, or when your customers already manage everything through Pages and Business Manager.
A note on scope names, because many guides get it wrong: in January 2025 Meta retired the short values business_basic, business_content_publish and so on for Instagram Login, replaced by the instagram_business_* names (Business Login docs). The Facebook Login permissions instagram_basic and instagram_content_publish are still the ones Meta lists for that version.
How to get access: app, permissions and App Review
- Convert the Instagram account to Business or Creator in the Instagram app settings. For Facebook Login, also link it to a Facebook Page.
- Create a Meta app of type Business at developers.facebook.com. Meta’s get-started guide says that if your existing app is not a Business app, you need a new one.
- Add the Instagram product and set up Business Login with your redirect URI.
- Generate a test token. In the App Dashboard, go to Instagram, then API setup with Instagram business login, and click Generate token next to the account. That token is valid for 60 days, enough to build and test.
- Decide your access level. Standard Access is the default and covers accounts you own or manage, plus development. To serve accounts owned by other people (any SaaS), you need Advanced Access, which requires App Review and business verification.
App Review is where most projects slow down. Meta reviews each permission you request, so ask only for the ones you use and have a screencast showing where each one appears in your product. Meta does not publish a guaranteed review time.
Access tokens: short-lived, long-lived and refresh
With Instagram Login, the production flow has three steps. First, send the user to the authorization window:
https://www.instagram.com/oauth/authorize
?client_id=YOUR_APP_ID
&redirect_uri=https://yourapp.com/auth/instagram/callback
&response_type=code
&scope=instagram_business_basic,instagram_business_content_publishInstagram redirects back with a code that is valid for one hour and can be used once. Then exchange and extend it:
# 1. Exchange the code (valid 1 hour, single use) for a short-lived token
curl -X POST https://api.instagram.com/oauth/access_token \
-F client_id=YOUR_APP_ID \
-F client_secret=YOUR_APP_SECRET \
-F grant_type=authorization_code \
-F redirect_uri=https://yourapp.com/auth/instagram/callback \
-F code=CODE_FROM_REDIRECT
# 2. Exchange the short-lived token for a 60-day token
curl "https://graph.instagram.com/access_token?grant_type=ig_exchange_token&client_secret=YOUR_APP_SECRET&access_token=SHORT_LIVED_TOKEN"
# 3. Refresh it (token must be at least 24 hours old and not expired)
curl "https://graph.instagram.com/refresh_access_token?grant_type=ig_refresh_token&access_token=LONG_LIVED_TOKEN"| Token | Lifetime | What to do |
|---|---|---|
| Authorization code | 1 hour, single use | Exchange immediately |
| Short-lived token | 1 hour | Exchange for a long-lived token |
| Long-lived Instagram token | 60 days | Refresh with ig_refresh_token once it is at least 24 hours old; each refresh gives 60 more days |
| Long-lived Facebook User token | About 60 days | Obtained with grant_type=fb_exchange_token |
| Long-lived Page token | No expiration date | Can still expire or be invalidated under certain conditions |
Sources: Business Login, refresh_access_token, long-lived Facebook tokens.
A token that is not refreshed within 60 days cannot be refreshed any more; the user has to log in again. Run a daily job that refreshes every token older than a few days, and store them encrypted.
With Facebook Login you also need the Instagram account ID. Call GET /me/accounts to list the user’s Pages, then GET /PAGE_ID?fields=instagram_business_account to get the ID of the linked Instagram account. With Instagram Login, the token already belongs to the Instagram account.
How publishing works: containers, then media_publish
Every post, whatever the format, goes through the same two calls (content publishing guide):
POST /IG_ID/mediacreates a media container. You pass a public URL to your file, and Meta downloads and processes it.POST /IG_ID/media_publishwithcreation_idpublishes the container and returns the media ID.
Between the two, videos need processing. Check GET /CONTAINER_ID?fields=status_code; the values are IN_PROGRESS, FINISHED, ERROR, EXPIRED and PUBLISHED. Meta recommends querying once per minute for no more than five minutes. A container that is not published within 24 hours expires.
The file must be on a publicly accessible server when Meta fetches it. Signed URLs that expire in seconds, hotlink protection and pages that return HTML instead of the file are the usual causes of “media could not be fetched.” If the account is linked to a Page that requires Page Publishing Authorization, you cannot publish until PPA is completed.
Publish a single image
# Step 1: create an image container (JPEG only, public URL)
curl -X POST "https://graph.instagram.com/v26.0/IG_ID/media" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"image_url":"https://cdn.example.com/photo.jpg","caption":"New drop is live"}'
# => {"id":"CONTAINER_ID"}
# Step 2: publish it
curl -X POST "https://graph.instagram.com/v26.0/IG_ID/media_publish" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"creation_id":"CONTAINER_ID"}'
# => {"id":"IG_MEDIA_ID"}
Images must be JPEG; Meta does not accept PNG or extended JPEG formats like MPO and JPS. Captions can be up to 2,200 characters. Single images also accept alt_text (up to 1,000 characters), user_tags with x/y coordinates, location_id and up to 3 collaborators.
Publish a Reel with the Instagram Graph API
Reels are the format most people come to the API for. Set media_type to REELS and pass video_url. share_to_feed controls whether the Reel also shows in the profile feed, and cover_url or thumb_offset (milliseconds) set the cover.
# Step 1: create a Reel container
curl -X POST "https://graph.instagram.com/v26.0/IG_ID/media" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"media_type":"REELS","video_url":"https://cdn.example.com/reel.mp4","caption":"3 tips in 30 seconds","share_to_feed":true}'
# => {"id":"CONTAINER_ID"}
# Step 2: poll until status_code is FINISHED (once a minute, up to 5 minutes)
curl "https://graph.instagram.com/v26.0/CONTAINER_ID?fields=status_code" \
-H "Authorization: Bearer ACCESS_TOKEN"
# => {"status_code":"IN_PROGRESS","id":"CONTAINER_ID"}
# => {"status_code":"FINISHED","id":"CONTAINER_ID"}
# Step 3: publish
curl -X POST "https://graph.instagram.com/v26.0/IG_ID/media_publish" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"creation_id":"CONTAINER_ID"}'
Reel video specs from Meta’s media reference: MOV or MP4, H264 or HEVC video with AAC audio, 3 seconds to 15 minutes, up to 300 MB, 23 to 60 frames per second, 9:16 recommended. The API also supports trial Reels, shown to non-followers first, through a trial_params field.
If your video is on disk and not on a URL, create the container with upload_type=resumable and send the bytes to Meta’s upload host:
# Create the container with upload_type=resumable instead of video_url
curl -X POST "https://graph.instagram.com/v26.0/IG_ID/media" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"media_type":"REELS","upload_type":"resumable","caption":"Uploaded from disk"}'
# Send the bytes to rupload
curl -X POST "https://rupload.facebook.com/ig-api-upload/v26.0/CONTAINER_ID" \
-H "Authorization: OAuth ACCESS_TOKEN" \
-H "offset: 0" \
-H "file_size: 48213302" \
--data-binary "@reel.mp4"Publish a carousel
A carousel takes three steps: one container per item with is_carousel_item=true, a parent container with media_type=CAROUSEL and the child IDs, then media_publish on the parent. Carousels hold 2 to 10 items and can mix images and videos. The caption goes on the parent; captions are not supported on carousel items.
# Step 1: one container per item, flagged as carousel items (2 to 10 items)
curl -X POST "https://graph.instagram.com/v26.0/IG_ID/media" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"image_url":"https://cdn.example.com/1.jpg","is_carousel_item":true}'
# repeat for each item (images and videos can be mixed)
# Step 2: the carousel container, with the caption and the child IDs
curl -X POST "https://graph.instagram.com/v26.0/IG_ID/media" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"media_type":"CAROUSEL","caption":"Before and after","children":"ITEM_ID_1,ITEM_ID_2,ITEM_ID_3"}'
# Step 3: publish the carousel container with media_publish, as abovePublish a Story
Use media_type=STORIES with image_url or video_url, then publish as usual. Story videos are limited to 60 seconds and 100 MB; story images to JPEG and 8 MB, with 9:16 recommended. Interactive stickers, link stickers and polls are not among the publishing parameters.
What the API cannot do
- No shopping tags and no filters on published media.
- No native scheduling:
media_publishpublishes now. Scheduling means running the calls later from your own queue. - No personal accounts, and no reading of accounts that have not authorized your app.
- No uploading of files directly in the
/mediacall except through the resumable video flow.
Instagram API with Python
You do not need an SDK: the Instagram API is plain HTTPS and JSON. This script publishes a Reel with requests, polling the container the way Meta recommends:
import time
import requests
API = "https://graph.instagram.com/v26.0"
IG_ID = "YOUR_IG_USER_ID"
TOKEN = "YOUR_LONG_LIVED_TOKEN"
HEADERS = {"Authorization": f"Bearer {TOKEN}"}
def publish_reel(video_url: str, caption: str) -> str:
# 1. Create the Reel container
r = requests.post(
f"{API}/{IG_ID}/media",
headers=HEADERS,
json={"media_type": "REELS", "video_url": video_url, "caption": caption},
timeout=30,
)
r.raise_for_status()
container_id = r.json()["id"]
# 2. Poll once a minute, for no more than 5 minutes (Meta's guidance)
for _ in range(5):
time.sleep(60)
s = requests.get(
f"{API}/{container_id}",
headers=HEADERS,
params={"fields": "status_code"},
timeout=30,
).json()
status = s.get("status_code")
if status == "FINISHED":
break
if status in ("ERROR", "EXPIRED"):
raise RuntimeError(f"Container {container_id} failed: {s}")
else:
raise TimeoutError(f"Container {container_id} still processing")
# 3. Publish
p = requests.post(
f"{API}/{IG_ID}/media_publish",
headers=HEADERS,
json={"creation_id": container_id},
timeout=30,
)
p.raise_for_status()
return p.json()["id"]
if __name__ == "__main__":
media_id = publish_reel("https://cdn.example.com/reel.mp4", "Posted with Python")
print("Published:", media_id)
For images, drop the polling loop (image containers are usually ready at once) and send image_url instead of media_type and video_url. In production, catch requests.HTTPError and read the error.code and error.error_subcode fields in the response body, which map to the table below.
Instagram API limits
| Limit | Value | Source |
|---|---|---|
| API-published posts | 100 per account in a 24-hour moving period; a carousel counts as 1 | Content publishing |
| Carousel items | 2 to 10 | Error codes |
| Caption | 2,200 characters | Media reference |
| Image | JPEG, up to 8 MB, aspect ratio 4:5 to 1.91:1 for feed | Media reference, error codes |
| Reel video | 3 s to 15 min, 300 MB, MOV or MP4 | Media reference |
| Story video | 60 s, 100 MB | Media reference |
| Container lifetime | 24 hours | Content publishing |
| Platform call budget | Calls within 24 hours = 4800 × number of impressions | Overview |
| Media list | GET /IG_ID/media returns at most the 10K most recent | Media reference |
Many guides still say 25 or 50 posts a day. Meta’s publishing guide says 100. Its content_publishing_limit reference still shows quota_total: 50 in the example response, so do not hardcode either number: read the real quota for each account.
curl "https://graph.instagram.com/v26.0/IG_ID/content_publishing_limit?fields=quota_usage,config" \
-H "Authorization: Bearer ACCESS_TOKEN"
# => {"data":[{"quota_usage":2,"config":{"quota_total":...,"quota_duration":86400}}]}Common Instagram API errors and how to fix them
| Code / subcode | Message | Fix |
|---|---|---|
| 9004 / 2207052 | The media could not be fetched from this uri. | Make the URL public and make sure it returns the file itself. |
| 9007 / 2207027 | The media is not ready for publishing. | Poll status_code and publish only after FINISHED. |
| 352 / 2207026 | The video format is not supported. | Re-encode to MP4 or MOV (H264 or HEVC, AAC audio). |
| 36003 / 2207009 | The submitted image aspect ratio cannot be published. | Crop feed images to between 4:5 and 1.91:1. |
| 36000 / 2207004 | The image is too large. | Keep images under 8 MB. |
| 36004 / 2207010 | Caption too long. | Max 2,200 characters, 30 hashtags, 20 @ tags. |
| 100 / 2207028 | Carousels need at least 2 and no more than 10. | Adjust the number of children. |
| 9 / 2207042 | You reached maximum number of posts allowed by API. | Wait for the 24-hour window to roll over. |
| -2 / 2207003 | It takes too long to download the media. | Retry; host the file on a fast CDN. |
| 25 / 2207050 | The Instagram account is restricted. | The user has to resolve it in the Instagram app. |
Source: Instagram Platform error codes. Read 28 September 2026.
Insights and comments
Insights. GET /IG_ID/insights returns account metrics and GET /IG_MEDIA_ID/insights returns metrics for one post. With Instagram Login you need instagram_business_manage_insights; with Facebook Login, instagram_manage_insights and pages_read_engagement. Meta notes that some metrics need 100 or more followers, user metrics are stored for up to 90 days, and missing data comes back as an empty set, not a zero (insights docs).
Comments. GET /IG_MEDIA_ID/comments lists comments, POST /IG_COMMENT_ID/replies replies, POST /IG_COMMENT_ID hides or unhides, DELETE /IG_COMMENT_ID deletes, and POST /IG_MEDIA_ID turns comments on or off. The permission is instagram_business_manage_comments (Instagram Login) or instagram_manage_comments (Facebook Login). Subscribe to the comments webhook instead of polling (comment moderation docs).
# Metrics for one post
curl "https://graph.instagram.com/v26.0/IG_MEDIA_ID/insights?metric=views,reach,likes,comments" \
-H "Authorization: Bearer ACCESS_TOKEN"
# Comments on a post, then a reply to one of them
curl "https://graph.instagram.com/v26.0/IG_MEDIA_ID/comments" \
-H "Authorization: Bearer ACCESS_TOKEN"
curl -X POST "https://graph.instagram.com/v26.0/IG_COMMENT_ID/replies" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"message":"Thanks! Link is in our bio."}' Messaging works through instagram_business_manage_messages with Instagram Login, or the Messenger Platform with Facebook Login. Meta lists separate rate limits for it, such as 2 calls per second for the Conversations API.
The shortcut: the same Reel in one Upload-Post call
Everything above is worth building if Instagram is your product. If you just need posts to go out, the part that costs time is not the three HTTP calls. It is the Meta app, App Review, token refresh, public file hosting, status polling, retries and the per-account quota. Upload-Post’s Instagram API runs an approved Meta app with Instagram Login, so you connect a Business or Creator account once in the dashboard (no Facebook Page needed) and send the file directly:
curl -X POST https://api.upload-post.com/api/upload \
-H "Authorization: Apikey YOUR_API_KEY" \
-F "[email protected]" \
-F "user=my-brand" \
-F "platform[]=instagram" \
-F "media_type=REELS" \
-F "title=3 tips in 30 seconds" \
-F "scheduled_date=2026-10-05T18:00:00" \
-F "timezone=America/New_York"
That uploads the file, creates the container, waits for processing and publishes at the scheduled time, up to 365 days ahead. Leave out scheduled_date to post now, use media_type=STORIES for a Story, or send add_to_queue=true to take the next free slot in your queue. Photos and carousels, including mixed image and video carousels, go through POST /api/upload_photos with photos[]. The Reel options map to Meta’s: share_to_feed, collaborators, cover_url, thumb_offset, user_tags and trial Reels via share_mode.
The same request can send the video to other networks too. In Python:
import requests
r = requests.post(
"https://api.upload-post.com/api/upload",
headers={"Authorization": "Apikey YOUR_API_KEY"},
files={"video": open("reel.mp4", "rb")},
data={
"user": "my-brand",
"platform[]": ["instagram", "tiktok", "youtube"],
"title": "3 tips in 30 seconds",
},
)
print(r.json())Where it fits and where it does not. Upload-Post covers publishing, scheduling, analytics of your own posts, comments and DMs on Instagram and 21 other platforms. It does not do hashtag search, competitor research or reading public accounts that have not connected; for that you still need Meta’s Facebook Login API or a data provider.
The Free plan is free forever with no card: 10 uploads a month and 2 profiles, with Instagram and API access included. Paid plans with unlimited uploads start at $24 a month ($16 billed annually). For no-code routes, see how to automate Instagram posts and how to schedule a post on Instagram. For other Meta surfaces, read the Facebook Graph API guide and the Threads API guide, or compare options in the social media API overview. Full parameters are in the Upload-Post docs.
Frequently asked questions
Is the Instagram API free?
Meta’s Instagram Platform documentation lists no fee or per-call price for the API. What it costs you is time: a Meta developer app, App Review and business verification if you serve accounts you do not own, token refresh logic, and handling of media processing and errors. If you only need publishing and scheduling, a service like Upload-Post takes over that work, and its Free plan includes Instagram.
Can I post to a personal Instagram account with the API?
No. Meta’s Instagram Platform only works with Instagram professional accounts, meaning Business or Creator accounts. Personal accounts are not supported for publishing, insights or comments. Switching to a professional account is free and done in the Instagram app settings.
Is the Instagram Basic Display API still available?
No. Meta shut it down on December 4, 2024, and every request to it has returned an error since then. It was the only official way to read personal accounts. Its replacement for business use is the Instagram API with Instagram Login, which needs a Business or Creator account.
How many posts per day can I publish with the Instagram API?
Meta’s content publishing guide says an Instagram account is limited to 100 API-published posts in a 24-hour moving period, and a carousel counts as one post. Query GET /<IG_ID>/content_publishing_limit to see quota_usage and the quota_total that applies to the account.
How do I get an Instagram API access token?
For testing, generate one in the App Dashboard under Instagram, API setup with Instagram business login; it lasts 60 days. In production, send users through Business Login at instagram.com/oauth/authorize, exchange the code at api.instagram.com/oauth/access_token for a short-lived token (1 hour), then exchange that for a 60-day long-lived token with grant_type=ig_exchange_token and refresh it with grant_type=ig_refresh_token before it expires.
Do Creator accounts work with the Instagram publishing API?
Yes. Meta’s docs say app users need an Instagram professional account, and both Business and Creator accounts qualify. Some older guides say only Business accounts can publish; that is not what the current documentation says.
Can I schedule Instagram posts with the API?
The Graph API has no scheduling parameter for Instagram: media_publish publishes immediately, and an unpublished container expires after 24 hours. To schedule, you store the post and run the container and publish calls at the right time from your own job runner. Upload-Post does that for you with scheduled_date and timezone, up to 365 days ahead.
Can I post Instagram Stories through the API?
Yes. Create the container with media_type=STORIES and either image_url or video_url, then publish it with media_publish. Story videos can be up to 60 seconds and 100 MB, and story images must be JPEG up to 8 MB. Stickers, links and polls are not part of the publishing parameters.
How do I use the Instagram API with Python?
Plain HTTP with the requests library is enough; no SDK is needed. Create a container with POST /<IG_ID>/media, poll GET /<CONTAINER_ID>?fields=status_code until it is FINISHED, then POST /<IG_ID>/media_publish with creation_id. The full script is in the Python section of this guide.
Can I read hashtags or competitor posts with the Instagram API?
Hashtag search is only available with the Instagram API with Facebook Login, which needs the account linked to a Facebook Page. It is not part of the Instagram Login version. Upload-Post does not cover hashtag or competitor research; it is built for publishing, scheduling, analytics of your own posts, comments and DMs.