1편에서 계정·앱·권한·테스터·토큰까지 발행 준비를 끝냈다. 이제 실제로 블로그 글을 인스타에 올리는 코드 차례다. 준비가 미로였으니 발행은 쉬울 줄 알았는데, 여기서도 함정이 줄줄이 나왔다. 이 글은 캐러셀과 릴스를 코드로 발행하며 만난 것들을 정리한 기록이다.

역할을 둘로 나눴다: 카드 만들기 vs 올리기
발행을 한 덩어리로 만들지 않고 둘로 쪼갰다.
- 카드 생성 — 블로그 글을 카드뉴스 여덟 장과 캡션으로 재구성한다.
- 발행 — 만들어진 카드를 인스타에 올린다.
파이프라인 전체가 이런 식이다. 각 조각이 입력과 출력만 명확하면, 하나를 바꿔도 나머지는 그대로 재사용된다. “카드는 마음에 드는데 발행만 고치고 싶다” 같은 상황에서 이 분리가 값을 한다.
캐러셀은 3단계로 올린다
Graph API의 캐러셀 발행은 한 번에 안 된다. 컨테이너를 만들고, 묶고, 발행하는 세 단계다.
# 1) 이미지마다 "캐러셀 아이템" 컨테이너를 만든다
child = post(f"{ig_id}/media", image_url=url, is_carousel_item=True)
# 2) 아이템들을 묶어 캐러셀 컨테이너를 만든다 (캡션 포함)
carousel = post(f"{ig_id}/media",
media_type="CAROUSEL",
children=child_ids,
caption=caption)
# 3) 발행한다
post(f"{ig_id}/media_publish", creation_id=carousel_id)
컨테이너는 만든 뒤 24시간 안에 발행하지 않으면 만료된다. 그리고 발행 사이에 컨테이너가 “처리 완료” 상태가 됐는지 확인하는 단계를 넣어야 안정적이다.
함정 1 — API는 PNG를 거부한다
카드 이미지는 PNG로 만들었는데, 발행이 자꾸 실패했다. 원인은 포맷이었다. Graph API는 이미지로 JPEG만 받는다. PNG는 거부한다.
그래서 발행 직전에 PNG를 JPEG로 변환하는 단계를 끼웠다. 카드 배경이 투명일 수 있으니 흰 배경 위에 합쳐서 저장한다. 별것 아닌 것 같지만, 이걸 모르면 원인을 못 찾고 한참 헤맨다.
이미지를 어디에 두나 — API는 URL만 받는다
또 하나. Graph API에 이미지를 올릴 때는 파일을 직접 업로드하는 게 아니라 공개된 URL을 넘긴다. 즉 이미지가 인터넷에서 접근 가능한 주소에 먼저 올라가 있어야 한다.
그래서 변환한 JPEG를 공개 오브젝트 스토리지(S3 호환)에 올리고, 그 공개 URL을 API에 넘기는 방식으로 처리했다. 발행 흐름이 이렇게 된다.
카드 PNG → JPEG 변환 → 오브젝트 스토리지 업로드(공개 URL) → 그 URL로 캐러셀 발행
로컬 파일을 바로 못 넘긴다는 제약 하나가 “이미지 호스팅”이라는 단계를 통째로 요구한다. 처음 설계할 때 놓치기 쉬운 지점이다.
릴스는 영상이라 한 겹 더
캐러셀만으로도 되지만, 도달을 넓히려면 릴스(영상)가 유리하다. 그래서 같은 카드를 릴스로도 만들기로 했다.
문제는 규격이다. 카드는 세로 4:5인데 릴스는 9:16이다. 남는 위아래를 그냥 비우면 허전하다. 그래서 FFmpeg로 이렇게 합성했다.
- 배경: 같은 카드를 꽉 채워 크롭한 뒤 블러 처리
- 전경: 카드 원본을 가로 폭에 맞춰 중앙에 배치
- 슬라이드마다 몇 초씩, 사이에 크로스페이드
빈 여백 대신 카드의 블러가 배경을 채우니, 흔한 “레터박스” 영상보다 훨씬 자연스럽다. 첫 장이 릴스 커버가 되므로 표지 카드를 맨 앞에 둔다.
함정 2 — 릴스 API는 음악 라이브러리를 못 쓴다
릴스라면 음악이 있어야 한다. 그런데 여기서 막혔다. API로 발행하는 릴스는 인스타의 음악 라이브러리를 쓸 수 없다. 앱에서 손으로 올릴 때 붙이는 그 유행곡들을, 코드 발행에서는 못 쓴다.
방법은 하나뿐이다. 오디오를 영상 파일에 직접 구워 넣는 것. 대신 저작권 문제가 없는 음원만 써야 한다. 유행곡을 넣으면 저작권 클레임이나 계정 제재로 이어진다. 무료이면서 상업·수익화까지 허용하는 음원을 받아, FFmpeg로 영상에 합치고 끝에 페이드아웃을 걸었다.
토큰은 60일, 그래서 자동 갱신
발행에 쓰는 토큰은 유효 기간이 60일이다. 그냥 두면 60일 뒤 만료돼 다시 인증해야 한다. 완전 자동을 지향하는데 60일마다 손이 가면 곤란하다.
다행히 토큰은 갱신할 때마다 다시 60일로 리셋된다. 그래서 주 1회 갱신을 예약(cron) 해뒀다. 만료 한참 전에 계속 연장되니 사실상 만료되지 않는다. 진짜로 만료 없는 토큰을 쓰려면 더 복잡한 설정이 있지만, 이 규모에는 자동 갱신으로 충분하다.
마지막 함정 — 클릭되는 링크는 프로필 하나뿐
발행은 다 됐는데, 정작 블로그로 보내는 링크가 문제였다. 인스타는 게시물 캡션에 클릭되는 링크를 넣을 수 없다. 클릭 가능한 링크는 프로필의 웹사이트(프로필 링크) 하나뿐이다.
그래서 유입 경로를 이렇게 설계했다.
게시물 캡션에 “전체 글은 프로필 링크에서” → 프로필의 웹사이트 링크에 블로그 주소
이걸 모르면 “왜 게시물에서 블로그로 못 가지?” 하고 한참 헤맨다. 캡션에 아무리 링크를 적어도 클릭이 안 되니, 프로필 링크를 반드시 걸어둬야 한다.
결과
캐러셀과 릴스, 두 게시물이 실제로 올라갔다.
이제 새 글이 나오면 카드를 만들고 “발행” 명령 한 번으로 인스타에 올라간다. 손으로 폰을 붙잡던 일이 사라졌다.
돌아보면 자동화 순서를 쉬운 순이 아니라 “수동이 제일 아픈 순”으로 정한 게 맞았다. 워드프레스는 발행이 어렵지 않은데, 폰으로 카드 여덟 장을 올리는 건 매번 고역이었다. 그래서 더 까다로운 인스타를 먼저 뚫었다.
정리 — 배운 것
- Graph API는 파일이 아니라 공개 URL을 받는다. 이미지 호스팅이 사실상 필수 단계다.
- 이미지는 JPEG만. PNG는 발행 직전에 변환한다.
- 릴스는 비즈니스 계정 + 음원 직접 삽입. 인스타 음악 라이브러리는 API에서 못 쓴다.
- 토큰은 60일이지만 갱신을 예약하면 사실상 영구다.
- 클릭되는 링크는 프로필 하나. 유입은 프로필 링크로 설계한다.
그리고 이 글도 곧 카드뉴스와 릴스로 만들어 인스타에 올릴 것이다. 인스타 자동화를 만든 이야기를, 그 자동화로 발행하는 셈이다.