Instagram Graph APIメモ;2025年3月版

Instagram Graph APIを使う手順とメモ。以前、[2020年頃に少し使い方のメモに関する記事を書いたが](https://qiita.com/ricemountainer/items/70e96f14715633fb9966)、「始め方」の部分が少し違っていたりしたのと、当時と比べて投稿系の処理がAPIで可能になっていることもあり、改めてここで整理しておく。

## アクセストークンの取得方法

大体以下のドキュメントに載っているがわかりづらいのでスクショ多めで手順を載せることにする。
https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/create-a-meta-app-with-instagram

なお、どうもAPIを使用するうえで前提となる認証の方式に「Instagram Login」と「Facebook Login」の2種類が存在するようで、「Instagram Login」の場合と「Facebook Login」の場合とで利用できるデータの種類やコールするAPIのホスト等が異なるようだ。以下に挙げる手順は「Instagram Login」に該当する。詳しくは以下で解説されている。
https://developers.facebook.com/docs/instagram-platform/overview

### 手順

Metaアプリを作成してからテストユーザーを設定するまでの流れは、Threadsの流れと大分近い。そちらは以下のGitHubリポジトリにまとめてあるので興味があれば。
https://github.com/ricemountainer/sample-threads-oauth-token

[Meta Developers](https://developers.facebook.com/)のサイトで「アプリを作成」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392942/e29af504-4767-4421-806e-6b694ed4a729.png)

アプリ名を適当に入力して「次へ」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392942/d7488f55-c543-4371-afa4-e474e0186ea6.png)

ユースケースは「その他」でいいらしい
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392942/f741239b-7bcc-4fd5-b93a-82262f85aed8.png)

アプリタイプは「Business」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392942/f902d1a5-af4f-49da-b9d9-9cd368bb8bc8.png)

ビジネスポートフォリオの部分は未選択のまま「アプリを作成」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392942/223f8b18-ab7c-4663-a554-c513ba6544cb.png)

作成したアプリのダッシュボード左メニューから「商品を追加」→「Instagram」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392942/0d1a729c-8d87-4875-80ca-6f5f2177e652.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392942/25bc9bec-805f-441c-9a01-070c86b86e01.png)
※これは選択済みの表示です

左メニューから「アプリの役割」＞「役割」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392942/ec16b38e-cebd-464a-97cb-ff74002c2a33.png)

「メンバーを追加」（見切れてるけど右側の青いボタン）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392942/9fa88dbd-9db1-4532-b0df-3082e510bd9f.png)

一番下の「Instagramテスター」を選択し、ユーザー名（＠の後ろ）を入力してsuggestされたユーザーを選択。自分のユーザー名を入力・選択してください。以下は私の[個人ユーザーricemountainer](https://www.instagram.com/ricemountainer/)を入力・選択してみた例です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392942/1194aa6c-b038-4b26-8a24-d0c5862b2c52.png)

するとさっき「メンバーを追加」したリストにユーザーが追加される。追加直後は以下の★マークの部分に「承認待ち」というマークが出る。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392942/195003e0-6657-4426-8716-9122c22a5d9f.png)

↑の「...アプリとウェブサイトセクションから招待を管理できます」のリンク部分をクリックする。と、Instagramの「アプリとウェブサイト」ページに遷移するので、そこで「承認」をクリックする（スクショ忘れ）なお、これはPCのブラウザでないと承認操作できない（Instagramのスマホアプリ（iPhone）では「アプリとウェブサイト」の許可セクションにこの「Instagramテスター」というメニュー自体が存在していなかった）無事に承認が完了すると↑の★部分の「承認待ち」の部分の表示が消える。

左メニューの「Instagram」＞「InstagramログインによるAPI設定」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392942/d7bd3356-83e0-4fb1-be04-37e92fb767e7.png)

「1. アクセストークンを生成する」の項に緑のチェックマークがつき、先ほど追加したユーザーが表示されていることが確認できる。その後「トークンを生成」リンクをクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392942/762944b3-6cc0-4201-83d2-73c8df705aab.png)

モーダルが開いてアクセストークンが表示される。忘れずにコピーして保存しておく。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392942/865c5c40-ee80-4e3c-8f89-b75e60f75a6e.png)

以後、基本的にはこのトークンを使ってAPIをコール可能である。Instagram Graph APIはアクセストークンを`access_token=xxx`の形でGETパラメータに付与してリクエストする。このアクセストークンは（発行までの過程で明示的に指定してないが）Instagram Graph APIコールに必要なPermissionを暗黙的に一通りそろえてくれている。[アクセストークンデバッガー](https://developers.facebook.com/tools/debug/accesstoken/)で調査すると保有しているPermissionは以下の通り。
```
instagram_business_basic, instagram_business_manage_messages, instagram_business_content_publish, instagram_business_manage_insights, instagram_business_manage_comments
```

ただしこのアクセストークンには期限があり、未来永劫使えるわけではない。これは後述する。

## Instagram Graph APIコール:参照系

例えば`/me`エンドポイントに対しては
```bash
$  curl https://graph.instagram.com/v22.0/me?access_token=...
{"id":"123"}
```

となる。
ここで取得した`id`値を使ってそのユーザー（自分）のメディア一覧を取得可能で、上の例`123`だと
```bash
$ curl https://graph.instagram.com/v22.0/123/media?access_token=...

{"data":[{"id":"2345..."},...]},"paging":{"cursors":{"before":"xxx...","after":"yyy..."},"next":"https:\/\/graph.instagram.com\/v22.0\/..."}}
```

となる。`data`フィールドの`id`の配列がメディアIDの一覧がになっており、単一の投稿の情報取得はこのIDを使って以下のように取得可能である。
```bash
$ curl "https://graph.instagram.com/v22.0/2345?fields=caption,like_count&access_token=..."
{"caption":"20250324 drawing\n\n\u30c9\u30e9...","like_count":5,"id":"2345"}
```

ここで`fields`パラメータはレスポンスに含める投稿のメタ情報を指定する。指定できるフィールドパラメータは以下参照。
https://developers.facebook.com/docs/instagram-platform/reference/instagram-media#-----

ちなみにcaptionに日本語文字を含む場合、レスポンスは上のようにいわゆる`native2ascii`された値で返ってくる。受信したデータを日本語として扱いたいワークロードがある場合は、レスポンスから取り出した後意図的な変換が必要になると思われるので注意が必要だ。

## Instagram Graph APIコール:投稿系

大体以下に載っている。
https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/content-publishing

基本的には、
1. (0) Instagramに投稿したい画像や動画をPublic Access可能なサーバー上にUPLOADしておく
1. (1) APIコール時に1.のURLを指定してアップロード
1. (2) 上記2.の処理が完了するまで待機
1. (3) 上記2.の処理が完了したら公開指示

って流れでようやく「公開」に至る。これはThreadsも同じ流れである。(0)はAPIの範疇ではないのでここでは割愛する。適当なS3バケットつくる等してアップロードなりしていただければ。。

なお、APIの認証方式がFacebookログインで、かつ投稿するのが動画である場合に限定だが、ローカルから直接アップロードする口が用意されているらしい。以下に記述がある。
https://developers.facebook.com/docs/instagram-platform/instagram-api-with-facebook-login/content-publishing#----------------

冒頭書いた通りこの記事はInstagramログインをAPIの認証方式とする手順や方法を解説するものになっており、個人的にもFacebookログインのほうは試したことがないので不明。参考記述として載せておく。

### 単一画像投稿

恐らく一番シンプル。

#### (1) POST /(ig-user-id)/media

まず`/(ig-user-id)/media`にPOSTを投げて投稿指示をする。`(ig-user-id)`は↑で`/me`エンドポイントのレスポンスから取得したIDである（この例では`123`）。

```bash
$ curl -X POST "https://graph.instagram.com/v22.0/123/media?image_url=https://hoge.com/huga.jpg&caption=20250329%20test%0Atest%0A%23manga&access_token=..."
{"id":"34567"}
```

以下がポイント。
- `123`はユーザーIDである。↑の`/me`のレスポンスで取得したID。
- `image_url`には上述の通り、Public Access可能な画像のURLを指定する。この例では`https://hoge.com/huga.jpg`である。ちなみに拡張子は`jpg`しか許可されていない。らしい。
- `caption`はURLエンコードされている必要がある。例えば改行は`%0A`、空白は`%20`となる。
- レスポンスの`{"id":"34567"}`はIGコンテナという、画像や動画のアップロードの進捗状況を管理するオブジェクトのIDを示している。後述で使用する。
- このときに`media_type=STORIES`を付与しておくとストーリーへの単一画像投稿になる。

#### (2) GET /(ig-container-id)

ドキュメントにも記載があるが、画像や動画のアップロードは非同期なので、完全に完了するまで待たなければならない。これの進捗状況を確認する場合、`POST /(ig-user-id)/media`のレスポンスのIGコンテナIDを使って、`fields=status_code`を含む形で、`/(ig-container-id)`のエンドポイントに以下のように問い合わせる。まだアップロード中だと以下のようなレスポンスになる。

```bash
$ curl "https://graph.instagram.com/v22.0/34567?fields=status,status_code&access_token=..."
{"status":"IN_PROGRESS","status_code":"IN_PROGRESS","id":"34567"}
```

完了してると以下のようなレスポンスになる。
```bash
{"status":"FINISHED","status_code":"FINISHED","id":"34567"}
```

`"status_code":"FINISHED"`なら完了しているらしい。ドキュメントは以下。
https://developers.facebook.com/docs/instagram-platform/instagram-graph-api/reference/ig-container

#### (3) POST /(ig-user-id)/media_publish

IGコンテナの`status_code`が"FINISHED"であることを確認後、` /(ig-user-id)/media_publish`に`creation_id=(ig-container-id)`の形でGETパラメータを付与してPOSTを投げる。
```bash
$  curl -X POST "https://graph.instagram.com/v22.0/123/media_publish?creation_id=34567&access_token=..."
{"id":"34567"}
```

このレスポンスはMedia IDであり、`GET /media`のレスポンスで返却されるID値と同質のものとなる。これにより実際に投稿されたのが以下。
https://www.instagram.com/p/DHv-EaeBpKl/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392942/99f17241-8daf-4d99-991a-ce7b9a22c94c.png)

### リール投稿

基本的に↑の「単一画像投稿」と同じ。ただ最初に指定する際のパラメータが多いのでちょっと大変。とりあえず試した範囲での情報を載せる。ドキュメントは以下。
https://developers.facebook.com/docs/instagram-platform/instagram-graph-api/reference/ig-user/media#--------

#### (1) POST /(ig-user-id)/media

`/(ig-user-id)/media`に`media_type=REELS`を指定して色々パラメータ付与してPOSTする。以下のような感じ

```bash
$ curl -X POST "https://graph.instagram.com/v22.0/123/media?media_type=REELS&video_url=https%3A%2F%2Fhoge.com%2F001_test.mp4&caption=20250401+test%0D%0Areel+test%3B%0D%0ACollected+some+mangapanels+from+my+original+manga+%22RESIGN+THREAT%22%21%0D%0Ahttps%3A%2F%2Fresign-threat.com%2Fstory%2Flatest%0D%0A%23manga+%23originalmanga+%23mangaart+%23mangapanel&share_to_feed=true&cover_url=https%3A%2F%2Fhoge.com%2Fcover.jpg&audio_name=testaudio&access_token=..."
{"id":"5678"}
```

ポイントとしては、、、
- `media_type=REELS`は必須。
- `video_url`にはリールに投稿する動画のURLを指定する。上の単一画像投稿と同じでPublic Access可能である必要がある。この例だと`https://hoge.com/001_test.mp4`でURLエンコードしている。が、そういえば単一画像投稿のときにはURLエンコードしなかったので、このパラメータのURLエンコードはもしかしたら必須ではないかもしれない。
- `share_to_feed`は「フィードに投稿するかどうか」で上の例だと`true`（フィードに投稿する）を指定している。
- `cover_url`はリール動画の「カバー」の画像URLを指定する。`video_url`と同じくPublic Access可能な場所に配置されている必要がある。
- `audio_name`はAudio名を指定する。これはリール投稿に表示される音源の名称をここで指定できる。この場合"testaudio"という文字列を指定している。詳細後述。
- `video_url`に指定する動画と、`cover_url`に指定するカバー画像には、アスペクト比やコーデック等に関して厳密な仕様が存在する。これを逸脱したデータを指定した場合にどういう動きをするのか不明。詳細は以下を参照。
  https://developers.facebook.com/docs/instagram-platform/instagram-graph-api/reference/ig-user/media#reels-specs

ドキュメントによれば他にもいくつかパラメータはあるが、とりあえず上に挙げた分だけ指定すればリールの投稿には成功した。

#### (2) GET /(ig-container-id)

上の単一画像投稿取得と同じなので割愛

#### (3) POST /(ig-user-id)/media_publish

上の単一画像投稿取得と同じなので割愛

実際に投稿されたのが以下
https://www.instagram.com/p/DH5VJgtphzk/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392942/9823a5d2-f1fe-4f7b-90e5-03f4ef47df4a.png)

スマホアプリで表示した場合が以下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392942/3f334640-1a7c-45f2-9b08-18e444a561cd.png)

赤い矢印でマーク付けた部分"testaudio"と書いてあるのが、(1) `POST /(ig-user-id)/media`で`audio_name`パラメータで指定した値だ。「この動画の音源はこういう名前の曲ですよ」という表示に使用できるということのようだ。なおこのテスト投稿の動画の音源はBulldozerというフリー音源である。
https://www.dova-s.jp/bgm/play12520.html

また、`share_to_feed`を`true`に指定したので、「投稿（フィード）」と「リール」の両方のタブに表示される。
「投稿」タブ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392942/7a031cd8-ae8b-47dd-9b51-4fd268d634fb.png)
「リール」タブ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392942/d956473f-b899-4633-a9c9-e53102d35a50.png)

### カルーセル投稿

複数の画像・複数の動画、あるいはそれらを組み合わせた投稿。対象となる画像や動画を事前にすべてUPLOADしておいて、それらのコンテナIDを配列で指定して公開させる。カルーセルの場合のみ、画像や動画のUPLOADのあと、`/media_publish`の前に、個別に「カルーセルそれ自体の投稿」という独自の手順が必要になる。詳細は以下。
https://developers.facebook.com/docs/instagram-platform/instagram-api-with-facebook-login/content-publishing#--------3

#### (1) POST /(ig-user-id)/media

カルーセル投稿の場合はこの手順が複数回必要になる。（投稿に載せる画像や動画の数分この手順を繰り返す。）個々のリクエスト内容は基本的には「単一画像投稿」と同じ。以下のような感じでリクエストを投げる。とりあえず簡単に画像2枚のカルーセル投稿を作ることを試みる。

```bash
$ curl -X POST "https://graph.instagram.com/123/media?is_carousel_item=true&image_url=https://hoge.com/huga-1.jpg&caption=20250402%20test%0Acarousel%20test1&access_token=..."
{"id":"9991"}
```

```bash
$ curl -X POST "https://graph.instagram.com/123/media?is_carousel_item=true&image_url=https://hoge.com/huga-2.jpg&caption=20250402%20test%0Acarousel%20test2&access_token=..."
{"id":"9992"}
```

ポイントとしては、、、
- カルーセル投稿に載せる画像や動画に関しては、個々の`/media`リクエストに際して`is_carousel_item=true`を付ける必要がある。
- 個々のリクエストに`caption`は入れてもエラーにはならないが（上の例はその検証のためにわざと入れてある）、次の手順で入力する`caption`が優先されるのでこの段階でつける意味はない。

#### (2) GET /(ig-container-id)

上でリクエストした個々の画像や動画に関してアップロードの進捗状況をチェックし`"status_code":"FINISHED"`であることを確認次第次に進む。対象が複数になっただけで基本的には上と同じなので割愛する。

#### (3-0) POST /(ig-user-id)/media (CAROUSEL)

カルーセル投稿の場合は`/media_publish`の前にこの手間が一つ必要になる。(1)でアップロードした画像や動画をまとめた投稿の元となる情報を登録する。以下のような感じ。

```bash
$ curl -X POST "https://graph.instagram.com/123/media?media_type=CAROUSEL&children=9991%2C9992&caption=20250402%20test%0Acarousel%20test&access_token=..."
{"id":"9993"}
```

ポイントとしては、、、
- `media_type=CAROUSEL`を付ける。必須。
- `children`に(1)でアップロードした画像や動画のコンテナIDを指定する。この際それぞれをカンマ(,)区切りで繋げる形で指定する。上の例は`children=9991,9992`である。なお、カンマは`%2C`でURLエンコードしなければならないっぽい。「っぽい」というのは上のドキュメントの記述に倣ってそうしているのが理由というだけで、カンマ自体は別にURLエンコード必須というわけでもないはずだからである（実際、アップロードの進捗状況を確認する際の`GET /(ig-container-id)`では`fields=status,status_code`をURLエンコードなしで指定できている）なのでURLエンコードが絶対必須かどうかは不明。
- 投稿のキャプションとしてはここで付与したパラメータが使用される。(1)で個々の画像や動画のアップロードの際に付与した`caption`は使用されない。したがってここで`caption`を付けない場合はキャプションなしのカルーセル投稿が出来上がることになる。

#### (3) POST /(ig-user-id)/media_publish 

あとは同じなので割愛する。

実際に投稿されたのが以下
https://www.instagram.com/p/DH76LREBADo/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392942/0adcb787-6685-4ae7-b568-ace48fc5ecaf.png)

画像2枚つづりのカルーセル投稿が作成されていることが確認できる。

## アクセストークンの期限

↑の手順で取得したアクセストークンは約60日で期限切れになる。これはThreadsも同様。[アクセストークンデバッガー](https://developers.facebook.com/tools/debug/accesstoken/)で有効期限が確認できる。

有効期限が切れる前に`/refresh_access_token`にGETを投げることでリフレッシュできる
https://developers.facebook.com/docs/instagram-platform/reference/refresh_access_token

```bash
$ curl "https://graph.instagram.com/refresh_access_token?grant_type=ig_refresh_token&access_token=...
{"access_token":"IG...","token_type":"bearer","expires_in":5184000,"permissions":"instagram_business_basic,instagram_business_manage_messages,instagram_business_content_publish,instagram_business_manage_insights,instagram_business_manage_comments"}
```

ただし、リフレッシュした場合も、リフレッシュに用いた旧アクセストークンはまだ有効である。（そのアクセストークン使ってAPI呼べる）よって、これは「リフレッシュ」っていうより「有効なトークンの追加生成」に近い。つまり、実行すればするほど、有効なトークンがどんどん増えるということになる。個人的にはリフレッシュしたタイミングで昔のトークンは破棄されてほしいのだが、そういう気の利いたことはしてくれないらしい。そのうえ、任意のタイミングで破棄（Revoke）するエンドポイントも提供されていないため、基本的に60日経過するまで待つ以外にトークンを失効させる手段が（おそらく）存在しない。これはThreadsも同様である。なのであまり多用しない方が良い。

## graph.facebook.comとgraph.instagram.comの違い、アクセストークンの扱い

Instagram Graph APIは`graph.facebook.com`（Facebookログインを使用）と`graph.instagram.com`（Instagramログインを使用）の2つにホストされている。冒頭書いた通り、この手順は後者`graph.instagram.com`に対してリクエストを送信する場合の手順や方法になっており、前者`graph.facebook.com`ではない。両者はAPIを実行するにあたっての認証の方式が異なっており、両者で出来ることも異なる。（っていうことらしい）以下のドキュメントにその辺が記述されている。
https://developers.facebook.com/docs/instagram-platform/overview

2025年4月現在のできること・できないことのマトリクスのスクリーンショット。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392942/b46c8b35-6933-40f8-9eff-238cc16dedb4.png)

ドキュメントのリンクが両者を横断して飛び回っているのでわかりづらいが、どうも`graph.facebook.com`のほうはアクセストークンをGETパラメータではなく`Authorization`ヘッダに設定して使うらしい。以下のドキュメントにその辺の片鱗が見える。（ただ、やったことないので本当に全部そうなのかわからない）
https://developers.facebook.com/docs/instagram-platform/instagram-api-with-facebook-login/content-publishing#----1------------------------------------------

Instagram ログインで生成したアクセストークンを`graph.facebook.com`に設定してもエラーになり実行できない。
```bash
$ curl "https://graph.facebook.com/123/media?access_token=..."
{"error":{"message":"Invalid OAuth access token - Cannot parse access token","type":"OAuthException","code":190,"fbtrace_id":"Ax..."}}
```

これは`Authorization`ヘッダに付けた場合も同じ。
```bash
$ curl -H "Authorization: OAuth ..."  "https://graph.facebook.com/123/media"
{"error":{"message":"Invalid OAuth access token - Cannot parse access token","type":"OAuthException","code":190,"fbtrace_id":"Ai..."}}
```

一方、`graph.instagram.com`に対しては、GETパラメータではなく`Authorization`ヘッダに付けた場合もなぜか動く。
```bash
$ curl -H "Authorization: OAuth ..."  "https://graph.instagram.com/123/media"
{"data":[{"id":"2345"},{"id":"3456"},...
```

個人的には「アクセストークンをGETパラメータに付ける」というのがどうにも気持ち悪いので、`Authorization`ヘッダに付けても動くんならそっちのほうがスッキリしていいんだが、ドキュメントにはその辺の明記がないので、実際想定内の挙動かわからない。