Instagram Graph API는 거짓말을 한다: 자동화 파이프라인에서 하루 날린 이야기
Instagram 자동화 파이프라인을 디버깅하는 데 하루를 통째로 썼다. 버그가 있다고 확신했는데, 알고 보니 API가 실패하지 않은 것을 실패했다고 알려주고 있었다.
1. 403이 떠도 게시물은 올라가 있다
media_publish를 호출했더니 이런 응답이 왔다:
{
"error": {
"message": "Application request limit reached",
"type": "OAuthException",
"code": 4,
"error_subcode": 2207051,
"is_transient": false
}
}
HTTP 403. 실패로 처리하고 DB 상태를 업데이트하지 않았다.
그런데 Instagram에 들어가보니 게시물이 이미 올라가 있었다.
재시도했을 때 중복 게시물이 생기는 걸 보고 알았다. Instagram은 미디어를 게시한 후 403을 반환하는 경우가 있다. 요청이 실패한 게 아니라 성공했는데 응답 코드가 틀린 것이다.
media_publish에서 403을 받으면 실패로 처리하기 전에 최근 미디어를 먼저 확인해야 한다:
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):
# 403이었지만 실제로는 게시 성공
return data[0]["id"]
resp.raise_for_status()
2. 일일 한도 안에 있어도 차단된다
Instagram의 공식 발행 한도는 하루 25개다. API로 확인할 수 있다:
r = await client.get(
f"{GRAPH_URL}/{user_id}/content_publishing_limit",
params={"fields": "config,quota_usage", "access_token": token}
)
# {"data": [{"config": {"quota_total": 100, "quota_duration": 86400}, "quota_usage": 0}]}
내 quota_usage는 0이었다. 한도에 전혀 걸리지 않았는데 403이 계속 났다.
원인은 1시간 안에 13개를 올린 것이었다. 일일 한도는 안 넘었지만 봇처럼 보이는 패턴이 별도의 제한을 유발했다. 공식 문서에는 없는 행동 차단이다.
구분하는 방법:
| Rate limit | 행동 차단 | |
|---|---|---|
quota_usage |
한도 근처 | 0, 멀쩡해 보임 |
is_transient |
보통 true
|
false |
| Instagram 앱에 표시 | 없음 | 없음 |
| 시간 지나면 풀리나 | 24시간 후 리셋 | 불명확, 수일 지속 가능 |
is_transient: false가 핵심 판단 기준이다. 기다려도 풀리지 않는다.
예방책은 루프로 한꺼번에 발행하지 않는 것뿐이다. 반드시 스케줄러로 간격을 두어야 한다.
3. 권한을 추가해도 기존 토큰은 달라지지 않는다
게시물 삭제를 위해 instagram_manage_contents 권한이 필요했다. Meta 개발자 콘솔에서 추가하고 앱 권한 목록에서 확인도 했다. 그리고 삭제를 시도했더니:
{"error": {"message": "(#10) Insufficient permissions", "code": 10}}
여전히 실패. 이유는 알고 나면 당연하다. OAuth 토큰은 발급 시점의 scope를 고정으로 가진다. 앱에 새 권한을 추가해도 기존 토큰은 바뀌지 않는다. 새로 발급받아야 한다.
현재 토큰의 실제 권한 확인:
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"]
# ['instagram_basic', 'instagram_content_publish', 'instagram_manage_contents']
새 권한이 없으면 Graph API Explorer에서 해당 scope를 체크하고 토큰을 재발급해야 한다.
어디서도 명확하게 문서화되지 않은 내용들이다. 첫 번째 케이스는 중복 게시물이 쌓이는 걸 보기 전까지 오후 내내 다른 데서 원인을 찾고 있었다.
Instagram Graph API v22.0으로 카드뉴스 자동 발행 봇을 만들면서 겪은 내용입니다.
Top comments (0)