Blog / Changes and status
Why Instagram API Integrations Break
By the InScrape API team · Published 2026-09-02 · 8 min read

It worked on Friday. On Monday it returns errors, or worse, returns a 200 with a field missing and nothing alerts until a customer notices.
You arrive with a symptom, not a diagnosis, so this is organised by symptom. Each section has the same two parts: how to confirm it is that one rather than something that looks like it, and what the fix actually is. Two of the fixes are "the data source you were using no longer exists", which is worth finding out in ten minutes rather than three days.
| What you are seeing | Most likely cause |
|---|---|
OAuthException, code 190, on every call |
Token expired, refresh job failing silently |
| Everything stopped, and it stopped in December 2024 | Basic Display API shutdown |
| Works for your team, fails for real users | App in Development mode, or a lapsed review |
| HTML where JSON should be | Login wall on a scraper |
| 429, or timeouts from one host only | Rate limit or IP block |
| One account restricted, code still fine | Account flagged for automated access |
| 200 OK, but a field is null or gone | Response shape changed underneath you |
Symptom: code 190 on every call
The token expired and the refresh did not happen. This is the single most common cause of a working integration going dark, and it is almost never a token-handling bug. It is a refresh job that has been failing quietly for weeks.
Confirm it. Ask Meta directly rather than guessing:
curl -G "https://graph.facebook.com/debug_token" \
-d "input_token=$USER_TOKEN" \
-d "access_token=$APP_ID|$APP_SECRET"
The response carries is_valid, expires_at and scopes. If is_valid is false, the subcode in the original error tells you which failure it was: 463 is an expired token, 460 means the user changed their password and invalidated the session, 458 means the user removed your app. Only the first is fixable without the user coming back.
Fix it. Long-lived Instagram tokens last around 60 days and have to be refreshed inside that window. A token that sits unused past expiry cannot be refreshed at all, and the only path back is putting the user through the OAuth flow again.
The refresh job is the part to actually change. In most broken integrations it looks like this: a cron task calls the refresh endpoint, gets a non-2xx, logs it at info, and exits zero. Nothing pages. Sixty days later every call is a 190. A refresh failure is a scheduled outage with a countdown attached, so it belongs in the same alerting bucket as a failed deploy, not in a log file.
Symptom: everything stopped, and it stopped in December 2024
Meta shut the Instagram Basic Display API down on 4 December 2024. Integrations that had run untouched for years stopped that day, and no amount of token refreshing brings them back, because the product the app was granted no longer exists.
Confirm it. You are calling graph.instagram.com for a personal account, your logs show the break landing on or immediately after 4 December 2024, and re-running the OAuth flow fails at the authorisation step rather than the token step.
Fix it. There is no drop-in replacement, and this is the important part: the successors, Instagram API with Instagram Login and Instagram API with Facebook Login, both require the connected account to be a Business or Creator account. Basic Display served personal accounts. Nothing in the official surface serves them now.
If your product's premise was "user connects their personal Instagram and we show them their own media", that premise no longer has an official data source. Migration is a product decision before it is a code change. The options, and what each one costs, are laid out in the Instagram Basic Display API alternative breakdown.
Symptom: it works for your team and fails for everyone else
The error says something like (#10) requires instagram_basic permission, or the call succeeds for you and 400s for a customer.
Confirm it. Open the App Dashboard and check three things in order: whether the app is in Development or Live mode, the current status of each permission you request, and whether there is an outstanding Data Use Checkup or business verification. An app in Development mode only works for people who hold a role on it, which produces exactly this symptom and looks nothing like a permissions problem from the client side.
Fix it. Re-submit the review, or complete the checkup. Meta's Data Use Checkup runs annually and restricts API access when it lapses; the deadline does not wait for your sprint. Budget calendar time, not engineering time, because the queue is the bottleneck.
Nobody outside Meta can fix an app review for you. What a public-data API removes is the need for one, and only for data that is public anyway; it does nothing for permissions over accounts your users have connected. The line between those two is covered in Graph API vs scraping API.
Symptom: HTML where JSON should be
Your scraper hit a login wall or a checkpoint interstitial. The failure usually surfaces two layers away from its cause, as a parser exception on a key that has always existed.
Confirm it. Log the response body and content type, not just the status. A login wall is frequently served as a 200 with text/html, so a plain response.ok check passes it straight through to a parser that then dies on something unrelated. One assertion catches it:
if "application/json" not in r.headers.get("content-type", ""):
raise RuntimeError(f"non-JSON response: {r.status_code} {r.text[:200]}")
Fix it. The wall is a decision about the shape of that request, not a bug you can patch. Backing off and changing egress moves the threshold; it does not remove it. If you are now building proxy rotation, session pools and a fingerprint budget, you have started operating scraping infrastructure as a second product, and the question is whether you meant to.
Symptom: 429s, or timeouts from one host only
Distinguish the official case from the scraper case, because they need opposite fixes.
On the Graph API you get told. Meta's error reference covers throttling as code 4 at app level, 17 at user level, 32 at page level and 613 for general rate exhaustion, and every response carries usage headers:
X-App-Usage: {"call_count":94,"total_cputime":22,"total_time":41}
Each figure is a percentage of your allowance, and you are throttled at 100. There is no published requests-per-second number to code against, which means the header is the contract: read it on every response and throttle on the highest value, rather than tuning a sleep until the errors stop.
On a scraper you get told nothing. There are no usage headers and no error code, only blocks and timeouts. Confirm it by running the same URL from a different network. If your laptop works and the worker does not, the difference is the egress IP, not your code.
Symptom: an account got restricted
Your requests are fine, but the Instagram account your automation runs on has been challenged or limited.
This one has a whole post, because the fix is structural rather than technical: the unit Instagram measures and penalises is the authenticated identity, so rotating accounts multiplies the problem instead of solving it. See the scraping warning breakdown.
Symptom: 200 OK, and a field is null or gone
The quietest failure, and the one that reaches customers.
Confirm it. Diff a stored response from before the break against one from today. If you do not keep raw responses, that is the actual finding: start keeping a sample per endpoint per day, because without one you cannot tell a schema change from a data change.
Two causes look identical at the parser. On the official API, a Graph API version reaching end of life gets calls automatically upgraded to the next available version, which can carry a different response shape; check the version string in your URL against Meta's changelog and pin it explicitly. On an unofficial surface, an internal key was simply renamed, and no changelog exists to check.
Fix it. Assert on the fields you actually use, at the boundary, and alert on the assertion rather than on the exception it eventually causes downstream. A field that goes null for a week before anyone notices is a monitoring gap, not a vendor problem.
The part nobody wants to hear
Every symptom above except the first is the same fact in a different costume: an integration built on a surface with no published contract has no contract.
The Graph API at least tells you what it promises, and then changes it on a schedule you can read. An internal endpoint promises nothing. So "it broke" is not an incident against a baseline of stability. It is the steady state, and the only real design question is who is on call for it.
| What breaks | Who fixes it, in practice |
|---|---|
| Token refresh | You |
| App review, permissions | Meta, on Meta's calendar |
| An API product being shut down | Nobody; you migrate |
| Blocks, checkpoints, IP reputation | Whoever operates the fetch layer |
| Parser drift after a silent change | Whoever operates the parser |
The bottom three rows are the ones buying rather than building actually moves, and that is the whole argument for a hosted API. Not that it never breaks — it breaks, for the same underlying reasons — but that the pager sits with someone whose only job it is.
Which is why the failure contract matters more than the uptime figure. Ours is fixed: completed lookups are charged at the endpoint rate, including 200 private-account lookup and Profile null results. Service failures may also be charged if resources were already used; inspect credits_charged on every response. The six codes, and which of them are worth retrying, are in the error reference.
Equally plainly, what none of this fixes: we cannot read private accounts, DMs, saved posts, or the reach and impressions figures Instagram shows an account owner, and we cannot refresh your tokens or sit your app review. If your integration broke on a token for an account your user connected, that one is yours, and the first section above is the whole of the help available. If it broke because you were reading public data through a surface that changes without notice, that part is movable. The endpoint list is the shape of what moves.
Try it with 100 free credits.
No credit card, credits never expire, and failed requests are not charged.

