DEV Community

JustJinoIT
JustJinoIT

Posted on

같은 게시물을 세 번 올리고 나서야 이유를 알았다

같은 게시물을 세 번 올리고 나서야 이유를 알았다

자동화 봇을 테스트하면서 limit이 자꾸 걸렸다. AI한테 물어봤더니 이런 말이 돌아왔다.

"앱이 개발 중 상태라 제약이 있어요. 24-48시간 기다리면 풀릴 거예요."

그럴싸했다. 믿었다.


Meta 콘솔 토끼굴

기다렸다. 또 안 됐다.

그럼 앱 게시 문제인가 싶었다. Meta 개발자 콘솔 들어갔다. AI가 알려준 버튼이 없었다. 메뉴가 달랐다. 다른 경로로 들어갔다. 권한 항목 찾아서 추가했다. 앱 검수 신청 화면 들어갔다. 뭔가 테스트를 완료해야 한다고 해서 실행해봤다. 다시 권한 뺐다가 넣었다. 토큰 재발급 받았다. 또 테스트.

인증 화면까지 들어갔더니 사업자 등록 정보를 넣으라고 했다. 거기서 멈췄다. 이건 아니다 싶었다.

Graph API Explorer 열어서 직접 뒤적이기 시작했다. 뭔가 나오긴 했는데 뭘 보고 있는 건지도 불분명했다.


"중복 자료가 왜 이렇게 많냐"

그러다 인스타그램을 직접 열어봤다. 같은 게시물이 세 개였다. 피드 전체 보니까 12개. 여러 기사에서 중복이 각각 쌓여있었다.

코드 흐름을 다시 따라갔다. 403 받으면 실패로 처리하고, DB 업데이트 안 하고, 다음 스케줄에 재시도. 그 재시도도 403. 또 재시도. 근데 인스타에는 매번 올라갔다. 몇 사이클 돌고 나니 이렇게 됐다.

받은 에러:

{
  "error": {
    "message": "Application request limit reached",
    "type": "OAuthException",
    "code": 4,
    "error_subcode": 2207051,
    "is_transient": false
  }
}
Enter fullscreen mode Exit fullscreen mode

rate limit이겠지 했다. 확인해봤다:

r = await client.get(
    f"{GRAPH_URL}/{user_id}/content_publishing_limit",
    params={"fields": "config,quota_usage", "access_token": token}
)
Enter fullscreen mode Exit fullscreen mode

quota_usage: 0. 한도엔 전혀 안 걸려있었다. x-app-usage 헤더도 0%. rate limit이 아니었다.

행동 차단인가. 근데 행동 차단이 됐으면 어딘가 뜰 거 아냐? 앱, Business Suite, 개발자 콘솔. 아무것도 없었다. 경고도, 배너도, 표시 하나도.

행동 차단은 어디에도 안 뜬다. 에러 응답의 is_transient: false가 유일한 신호다. rate limit은 보통 is_transient: true에 24시간 후 풀린다. false면 기다린다고 해결 안 된다. 이게 구분 기준이었다.

원인은 테스트하면서 1시간 안에 게시물 13개를 올린 거였다. 일일 한도 25개엔 한참 못 미치는데. 단시간에 몰아 올리면 자동화 패턴으로 감지돼서 별도 제한이 걸린다. 문서엔 없다.

이 차단 상태에서 media_publish는 403을 뱉는데 게시물은 올라간다. 코드가 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):
                    return data[0]["id"]

    resp.raise_for_status()
Enter fullscreen mode Exit fullscreen mode

403 받으면 바로 최근 미디어 조회. 60초 이내에 올라온 게 있으면 성공으로 친다.


"추가했는데 해볼래?" 그리고 또 실패

중복 12개 지워야 했다. 삭제엔 instagram_manage_contents 권한이 필요한데 내 토큰엔 없었다.

권한 추가. 확인. 삭제 시도.

{"error": {"message": "(#10) Insufficient permissions", "code": 10}}
Enter fullscreen mode Exit fullscreen mode

실패. 다시. 같은 에러. 콘솔엔 분명히 있는데.

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"]
Enter fullscreen mode Exit fullscreen mode

instagram_manage_contents가 없으면 Graph API Explorer에서 scope 체크하고 재발급. 그 다음엔 됐다.


진짜 원인 조합

신호 의미
quota_usage: 0인데 403 행동 차단 (rate limit 아님)
is_transient: false 기다려도 안 풀림
403인데 인스타에 게시됨 차단 상태에서도 게시는 통과함
행동 차단이 UI에 표시됨 안 됨, 어디에도

API 동작은 문서에 없고, AI는 자신 있게 틀린 경로를 알려줬고, 그 경로의 콘솔 UI는 또 바뀌어있었다. 어디서부터 잘못됐는지 찾는 게 제일 어려웠다.

AI가 알려주는 콘솔 메뉴는 실제랑 다를 수 있다. API가 이상하다 싶으면 UI 말고 응답 필드 먼저 봐라. is_transient 하나가 대시보드보다 정직했다.

오후 하나 날렸다. 중복 12개, Meta 토끼굴, 권한 삽질. 그나마 건진 건 이 글이다.


Instagram Graph API v22.0. 카드뉴스 자동 발행 봇 만들면서 겪은 내용. 봇은 @dogfootbro.ai에 올라간다.

Top comments (0)