instagrapi vs the Instagram Graph API: Posting from Python Without Getting Banned
instagrapi drives Instagram’s private mobile API and gets accounts challenged or banned. Here is what it is, when it still makes sense, and how to post from Python the official way.
TLDR: instagrapi is a well-maintained Python library that logs into Instagram with your password and pretends to be the mobile app. It can do things the official API never will, and Instagram treats it as what it is: unauthorised automation. Its own README now says it is “best suited for testing, research, and controlled internal automation” and tells you to prefer the official APIs for business workflows. The official path from Python is a few requests calls against the Graph API with a Business or Creator account, a 60-day token, and a container-then-publish flow. If you publish for other people’s accounts, the choice is not close. Checked September 3, 2026.
What is instagrapi?
instagrapi describes itself as a “fast and effective unofficial Instagram API wrapper for Python.” Under the hood it combines Instagram’s public web endpoints with the private API the Instagram mobile app uses, signs requests to look like an Android device, and persists the resulting session to a JSON file so you can reuse it. It is MIT licensed, has about 6,700 stars, and is not abandonware: version 2.18.18 shipped on August 26, 2026, and the repository had commits the week this was written.
Logging in means handing the library a username and password. It supports two-factor codes, backup codes, and reusing a sessionid cookie. When Instagram gets suspicious it raises ChallengeRequired, and instagrapi’s challenge resolver can feed it an SMS or email code, or even reset the password automatically. It cannot get past everything; the same page states that selfie and manual-review challenges “are account review decisions by Instagram” and that the library “does not provide a generic bypass.”
The feature list is the reason people reach for it. instagrapi can upload photos, videos, albums, reels, and stories, but it can also send direct messages to any user, follow and unfollow, like, view stories, download other accounts’ media, and scrape hashtags and locations. It ships a realtime MQTT client for DMs and a dedicated best-practices document about “sessions, proxies, and anti-abuse handling,” which tells you what running it at scale involves.
One naming trap: searches for “instagram-py” surface Instagram-Py, a password brute-force tool that rotates Tor exits to evade lockouts. It is unrelated to instagrapi, it does not publish anything, and using it against an account you do not own is a crime.
Why do accounts get banned with instagrapi?
Three reasons, and they compound.
It is prohibited. Instagram’s Terms of Use say you “can’t attempt to create accounts or access or collect information in unauthorized ways,” including “in an automated way … without our express permission, regardless of whether such automated access or collection is undertaken while logged-in to an Instagram account.” Meta’s Platform Terms add that you “may not proxy, request, or collect Product usernames or passwords.” A library whose login function takes a password and emulates the app is on the wrong side of both sentences.
Instagram actively detects it. The maintainers’ README puts it plainly: “Private API automation is fragile in production because account trust, proxies, device state, challenges, and rate limits can change independently of the library. For account-owned business workflows, prefer official Instagram APIs where they cover your use case.” The issue tracker is the evidence. Issue #2718, opened July 4, 2026 against a current release, reports challenge_required on login with Instagram demanding that the checkpoint be completed manually. Issue #1806 reports an account suspended after scraping ten posts, paid proxy and all. Issue #1559 is simply titled “Suspended account.” These are not edge cases; they are the steady state of driving a private API.
Meta sues. In July 2022 Meta filed suit against Octopus, a scraping-as-a-service company, and against an individual who scraped more than 350,000 Instagram profiles. The one case Meta lost, Meta v. Bright Data in January 2024, turned on scraping public data while logged out; the court held the terms do not reach that. instagrapi is the opposite: logged-in automation with your users’ credentials. That is the scenario the terms were written for.
For a hobby script on your own account the practical risk is a checkpoint you clear by hand. For a product that publishes on behalf of customers, the risk is that their accounts get suspended because of your code, and you have no one to appeal to.
When is instagrapi still the right tool?
Being honest about the alternative is the point of this article, so here is where the unofficial library wins.
- Research and testing on accounts you own. The maintainers say this themselves. Reproducing an app’s behaviour, prototyping, or checking how Instagram renders a post before you build the real integration.
- Features the official API does not expose. The Graph API lets an app publish, read insights, and manage comments and messages for accounts that authorised it. It has no endpoint to follow someone, like a post, view a story, or message a stranger. If your internal tool needs one of those on your own account, there is no official route, and you accept the terms risk knowingly.
- Reading public profiles without a login. Even here instagrapi is the wrong fit. Instaloader downloads public posts and metadata without credentials, so a challenge never lands on an account you care about. Its own disclaimer applies: “in no way affiliated with, authorized, maintained or endorsed by Instagram.”
It is never the right tool for publishing to other people’s accounts from a product. That is what the official API is for, and it is less work than most Python developers expect.
instagrapi vs Instagram Graph API side by side
| instagrapi | Instagram Graph API | |
|---|---|---|
| How it authenticates | Username and password, emulated device, session file | OAuth 2.0; the user consents in Instagram, you receive a token |
| Account types | Any, including personal | Business and Creator only |
| Approval | None | App Review plus Business Verification to publish for accounts you do not own |
| Publishing | Photos, videos, albums, reels, stories, from local files | Images, carousels, reels, stories, from public URLs, via containers |
| Direct messages | To anyone | Only with a separate messaging permission, for the authorising account’s inbox |
| Follow, like, view stories | Yes | No |
| Scrape other accounts | Yes | No |
| Insights | Whatever the app shows | Documented metrics per post and per account |
| Terms of service | Violates them | Complies |
| Stability | Breaks when Instagram changes the app; proxies and device state are your problem | Versioned API with a changelog and deprecation windows |
| Ban risk | Real and documented | None for compliant use |
| Cost | Free library; proxies, spare accounts, and lost accounts are the cost | Free API; review and engineering time are the cost |
How do you post to Instagram with Python using the official API?
There is no official Meta Python package for Instagram publishing. The facebook-business SDK covers the Marketing API, ads, catalogs, and Business Manager, not content publishing. That is less of a problem than it sounds, because the publishing API is small enough that requests is the SDK.
The full walkthrough, including app setup, review, media specs, quotas, and error codes, is in our Instagram Graph API guide. The Python-only version is below.
1. Turn a login into a 60-day token
After the user authorises your app you hold a short-lived token that lasts an hour. Exchange it once, then refresh it before day 60. Both calls are documented on Meta’s Business Login page.
import requests
IG = 'https://graph.instagram.com'
def to_long_lived_token(short_lived_token: str, app_secret: str) -> dict:
response = requests.get(
f'{IG}/access_token',
params={
'grant_type': 'ig_exchange_token',
'client_secret': app_secret,
'access_token': short_lived_token,
},
timeout=30,
)
response.raise_for_status()
return response.json() # access_token, token_type, expires_in (seconds, ~60 days)
def refresh_long_lived_token(long_lived_token: str) -> dict:
# Only works if the token is at least 24 hours old and not yet expired.
response = requests.get(
f'{IG}/refresh_access_token',
params={'grant_type': 'ig_refresh_token', 'access_token': long_lived_token},
timeout=30,
)
response.raise_for_status()
return response.json()
Keep app_secret on the server. instagrapi users are used to storing a password in a config file; the official flow never sees the user’s password at all, which is the whole point.
2. Publish an image from a public URL
Instagram does not accept file uploads for publishing. You host the JPEG somewhere public, create a container that points at it, wait for the container to finish, then publish.
import time
import requests
IG = 'https://graph.instagram.com/v26.0'
def _post(path: str, params: dict, token: str) -> dict:
response = requests.post(f'{IG}{path}', data={**params, 'access_token': token}, timeout=30)
data = response.json()
if not response.ok or 'error' in data:
raise RuntimeError(data.get('error', data))
return data
def _wait_until_finished(container_id: str, token: str) -> None:
for _ in range(5):
status = requests.get(
f'{IG}/{container_id}',
params={'fields': 'status_code', 'access_token': token},
timeout=30,
).json().get('status_code')
if status == 'FINISHED':
return
if status in ('ERROR', 'EXPIRED'):
raise RuntimeError(f'container {status}')
time.sleep(60)
raise RuntimeError('container not ready after 5 minutes')
def publish_image(ig_user_id: str, token: str, image_url: str, caption: str) -> str:
container = _post(f'/{ig_user_id}/media', {'image_url': image_url, 'caption': caption}, token)
_wait_until_finished(container['id'], token)
published = _post(f'/{ig_user_id}/media_publish', {'creation_id': container['id']}, token)
return published['id']
3. Publish an album as a carousel
instagrapi’s album_upload becomes three steps: one child container per item, a parent container that lists the children, and a publish call.
def publish_carousel(ig_user_id: str, token: str, items: list[dict], caption: str) -> str:
"""items: [{'image_url': ...} or {'video_url': ..., 'media_type': 'VIDEO'}], 2 to 10 entries."""
child_ids = []
for item in items:
child = _post(f'/{ig_user_id}/media', {**item, 'is_carousel_item': 'true'}, token)
_wait_until_finished(child['id'], token)
child_ids.append(child['id'])
parent = _post(
f'/{ig_user_id}/media',
{'media_type': 'CAROUSEL', 'children': ','.join(child_ids), 'caption': caption},
token,
)
_wait_until_finished(parent['id'], token)
return _post(f'/{ig_user_id}/media_publish', {'creation_id': parent['id']}, token)['id']
Images must be JPEG, at most 8 MB, between 4:5 and 1.91:1; the carousel crops every item to the first item’s ratio. Reels are media_type='REELS' with a video_url, and stories are media_type='STORIES'. The account can publish 100 posts through the API per rolling 24 hours.
Migrating from instagrapi: what changes
If you have an instagrapi script and want to move it to the official API, this is the map.
| instagrapi call | Official equivalent | What changes |
|---|---|---|
Client().login(user, password) |
OAuth authorisation, then access_token exchange |
No password. The user consents once; you store a 60-day token and refresh it. |
cl.dump_settings() / cl.load_settings() |
Store the long-lived token | No device fingerprint or session file to protect. |
cl.photo_upload(path, caption) |
POST /media with image_url, then /media_publish |
Local file becomes a public URL; JPEG only; poll status_code. |
cl.album_upload(paths, caption) |
Child containers plus a CAROUSEL parent |
Up to 10 items; ratios normalised to the first item. |
cl.clip_upload(path, caption) |
media_type=REELS with video_url |
MP4 or MOV, 3 s to 15 min, 300 MB or less; share_to_feed controls feed placement. |
cl.photo_upload_to_story(path) |
media_type=STORIES |
No caption; 9:16. |
cl.direct_send(text, user_ids) |
Messaging permission on the authorising account only | No messaging strangers. Requires instagram_business_manage_messages and review. |
cl.user_follow(...), cl.media_like(...) |
None | Not available. |
cl.user_medias(...), cl.hashtag_medias_recent(...) |
/{ig-user-id}/media for the authorising account only; hashtag search is limited |
No scraping of other accounts. |
cl.insights_media(...) |
GET /{media-id}/insights |
Documented metric names; views replaced impressions. |
ChallengeRequired handling, proxies |
Nothing | The failure mode no longer exists. |
Two new obligations arrive with the official API: your app needs App Review and Business Verification before it can publish for accounts you do not own, and your users need Business or Creator accounts. Both are one-time costs. The recurring cost you lose is the one instagrapi users know well: waking up to a challenge screen.
The shorter path: one Python call
The official flow above is roughly 80 lines once you add token refresh, media hosting, error handling, and the same again for reels and stories. Then LinkedIn, TikTok, and the rest each want their own version. PostZen’s social media API and its Python SDK collapse that into one call per post, with OAuth, token refresh, media hosting at public URLs, container polling, and quota checks handled on our side. Instagram’s rules still apply; a unified API does not lift the 100-post cap or skip review, because we did the review once for everyone.
pip install postzen-sdk
from postzen import PostZen
client = PostZen() # reads POSTZEN_API_KEY, or pass PostZen(api_key="...")
response = client.posts.create_post(
content="Three views of the new release.",
media_items=[
{"url": "https://cdn.example.com/release-1.jpg"},
{"url": "https://cdn.example.com/release-2.mp4"},
{"url": "https://cdn.example.com/release-3.jpg"},
],
publish_now=True,
platforms=[
{
"platform": "instagram",
"account_id": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
"settings": {"post_type": "carousel"},
},
],
x_request_id="release-2026-09-03", # idempotent: a retry returns the same post
)
print(response.post.field_id)
post_type accepts feed, carousel, reel, and story, and the account ID comes from the accounts endpoint after the user connects through a hosted OAuth page. The same call publishes to nine other networks by adding entries to platforms. Details: the Instagram integration docs, the SDK overview, the Instagram page, and, for how Instagram compares with the other networks on access and limits, the best social media APIs for developers. Whatever you pick, pick it knowing what instagrapi is: a brilliant piece of reverse engineering that Instagram is entitled to break, and does.
Frequently asked questions
Is instagrapi allowed by Instagram?
No. Instagram’s Terms of Use prohibit accessing or collecting information in automated ways without express permission, and Meta’s Platform Terms forbid proxying usernames and passwords. instagrapi logs in with your password and emulates the mobile app, which is exactly that.
Will instagrapi get my account banned?
It can. The library’s own README calls private-API automation fragile in production, and its issue tracker documents accounts suspended after a handful of actions even behind paid proxies. Instagram’s challenge and selfie-review flows cannot be bypassed.
Is there an official Python SDK for posting to Instagram?
No. Meta’s facebook-business package covers the Marketing API, not content publishing. The official route from Python is plain HTTP requests to the Graph API: create a media container, poll its status, publish.
What can instagrapi do that the official API cannot?
Act as a logged-in user: send direct messages to anyone, follow and like, view stories, and download other users’ media or scrape hashtags and locations. The official API only lets you publish, read insights, and manage comments and messages for accounts that authorised your app.
Can I post to Instagram from Python with a personal account?
Not through the official API; it requires a Business or Creator account. instagrapi will technically post from a personal account, with the ban risk that comes with it.
What is instagram-py?
A password brute-force tool, not an API library. It has nothing to do with publishing and using it against accounts you do not own is illegal.



