I published the same post three times before I understood what was happening
I was testing an automation bot and kept hitting limits. Asked an AI assistant what was going on.
"The app is in development mode, so there are restrictions. Wait 24-48 hours and it should clear up."
Sounded reasonable. I believed it.
The Meta console rabbit hole
Waited. Still broken.
Maybe it's an app publishing issue, I thought. Went into the Meta developer console. The button the AI told me about wasn't there. The menu was different. Found another path. Located the permissions section and added what I needed. Got into the app review screen. It said I had to complete some tests first, so I ran them. Removed permissions, added them back. Got a new token. Tested again.
Eventually got into the verification flow and it asked for business registration info. That's where I stopped. This isn't right.
Opened Graph API Explorer and started poking around directly. Something was coming back but I wasn't sure what I was looking at.
"Why are there so many duplicates?"
At some point I just opened Instagram directly. Same post, three times. Looked at the whole feed: 12 duplicates across multiple articles, each one stacked up from previous runs.
Traced through the code. On 403, treat as failure, skip DB update, retry next cycle. That retry also 403'd. Retried again. But Instagram got the post every time. A few cycles in and this is what happened.
The error:
{
"error": {
"message": "Application request limit reached",
"type": "OAuthException",
"code": 4,
"error_subcode": 2207051,
"is_transient": false
}
}
Rate limit, obviously. Checked:
r = await client.get(
f"{GRAPH_URL}/{user_id}/content_publishing_limit",
params={"fields": "config,quota_usage", "access_token": token}
)
quota_usage: 0. Not even close to the cap. x-app-usage header was 0% too. Not a rate limit.
Maybe a behavior block then. But if you're behavior-blocked, wouldn't something show up somewhere? Checked the app, Business Suite, developer console. Nothing. No warning, no banner, no indicator.
Behavior blocks don't show up anywhere. The only signal is is_transient: false in the error response. Rate limits are usually is_transient: true and reset after 24 hours. false means waiting won't help. That's the only way to tell them apart.
The cause: 13 posts within one hour during testing. Well under the documented daily limit of 25, but posting that fast triggers a separate undocumented restriction. Instagram sees the pattern as automated. Nothing in the docs about this.
In this blocked state, media_publish returns 403 but the post goes through. Code treated 403 as failure, retried, and each retry stacked another duplicate.
Fixed it like this:
async def _publish_container(client, container_id):
resp = await client.post(
f"{GRAPH_URL}/{IG_USER_ID}/media_publish",
params={"creation_id": container_id, "access_token": IG_TOKEN},
)
if resp.is_success:
return resp.json().get("id")
if resp.status_code == 403:
await asyncio.sleep(3)
check = await client.get(
f"{GRAPH_URL}/{IG_USER_ID}/media",
params={"fields": "id,timestamp", "limit": 1, "access_token": IG_TOKEN},
)
if check.is_success:
data = check.json().get("data", [])
if data:
ts = datetime.fromisoformat(data[0]["timestamp"].replace("Z", "+00:00"))
if datetime.now(timezone.utc) - ts < timedelta(seconds=60):
return data[0]["id"]
resp.raise_for_status()
After a 403, immediately check recent media. If something posted within 60 seconds, call it a success.
"I added the permission. Try it now." Still failing.
Had to delete the 12 duplicates. Deletion requires instagram_manage_contents. My token only had instagram_content_publish.
Added the permission. Confirmed it in the console. Tried the delete.
{"error": {"message": "(#10) Insufficient permissions", "code": 10}}
Failed. Tried again. Same error. The permission was right there in the console.
OAuth tokens carry a fixed scope from when they were issued. Adding a permission to your app doesn't update existing tokens. Had to generate a new one.
To see what a token actually has:
r = await client.get(
"https://graph.facebook.com/v22.0/me/permissions",
params={"access_token": token}
)
granted = [p["permission"] for p in r.json()["data"] if p["status"] == "granted"]
If instagram_manage_contents isn't there, go to Graph API Explorer, check the scope, regenerate. After that it worked.
What was actually going on
| Signal | Means |
|---|---|
quota_usage: 0 but still 403 |
Behavior block, not rate limit |
is_transient: false |
Waiting won't fix it |
| 403 on publish but post appears | Block lets posts through anyway |
| Behavior block visible in any UI | No, nowhere |
The API behavior wasn't documented. The AI confidently pointed me in the wrong direction. The console UI it described had changed. Finding where things actually went wrong was the hardest part.
AI-suggested console paths may not match what's actually there. If the API is acting strange, check the response fields before the UI. is_transient was more honest than any dashboard.
Lost an afternoon. 12 duplicates, one Meta rabbit hole, one permission dead end. This post is what I got out of it.
Instagram Graph API v22.0. Built while automating a card news account. Bot posts to @dogfootbro.ai.
Top comments (0)