サイトのトップページに、Instagramの最新投稿を並べる。よくある要望です。うちも何本か作りました。

このとき気をつけないといけないのが、納品したときは動いていて、2か月後に止まるという壊れ方をすることです。
お客様は毎日サイトを見ているわけではありません。気づくのは、たいてい誰かに指摘されたときです。「サイトのインスタ、真っ白ですよ」と。
原因はアクセストークンの有効期限です。Instagramのトークンは60日で切れます。
自動で延ばす仕組みを入れておかないと、必ずそうなります。私が実際にハマったところを順に書きます。
まず、自分で実装しないで済むか考える
最初に選択肢を整理しておきます。自前で書くのは最後の手段です。
- 投稿1件だけを貼りたい … Instagramの投稿から「埋め込み」のコードをコピーして貼る。これがいちばん堅いです。ただし複数枚を並べたグリッド表示はできません
- WordPressのサイト … プラグインで済ませたほうが早いです。うちは有料版を1つ持っていて、トークンの更新も含めて面倒を見てくれます。月々の保守に入っているサイトなら、これがいちばん事故が少ないと思っています
- 静的HTMLのサイト、または見た目を自由に組みたい … ここで初めて自前になります
うちが自前で書いているのは3つめのケースです。デザインに合わせてタイルの形や枚数を変えたい、という要望が来たときですね。
トークンには2種類ある。見分けは先頭の文字
ここでまず1回つまずきました。
Instagramのアクセストークンには、取得の経路によって別種のものがあります。見た目で区別がつきます。
先頭 | 何用か | 叩くエンドポイント |
|---|---|---|
| Instagramのアカウントで直接ログインして取ったもの |
|
| Facebookのアカウント経由で取ったもの |
|
トークンとエンドポイントの組み合わせが違うと、エラーになります。
ネット上の解説記事は、書かれた時期によってどちらの前提かが違います。手順どおりにやったのに動かない、というときは、手元のトークンの先頭2〜4文字を見てください。ここが食い違っていることが多いです。
以前あった「Instagram Basic Display API」は終了しているので、いまから作るなら前者です。
60日で失効する。だから7日ごとに延ばす
本題です。取得できるトークンは長期トークン(long-lived token)と呼ばれるもので、有効期限は60日です。
そして、この期限はリフレッシュのAPIを叩けば、そこからまた60日に延びます。ただし条件があって、発行から24時間以上経っていることが必要です。
つまり、60日以内に一度でも叩いていれば、永久に生き続けます。逆に61日放置すると、そこで死にます。死んだトークンは復活できないので、手作業で取り直しです。
うちはサーバー側のPHPで、7日ごとに自動でリフレッシュしています。
// 前回のリフレッシュから7日以上経っていたら延長する
if (time() - $saved['refreshed_at'] > 7 * 86400) {
$res = json_decode(file_get_contents(
'https://graph.instagram.com/refresh_access_token'
. '?grant_type=ig_refresh_token&access_token=' . $saved['token']
), true);
if (!empty($res['access_token'])) {
$saved['token'] = $res['access_token'];
$saved['refreshed_at'] = time();
save($saved); // ファイルに書き戻す
}
}
なぜ60日ぎりぎりではなく7日なのか。失敗する余裕を持たせるためです。
Instagram側が一時的に落ちていたり、サーバーの外向き通信が止まっていたりすると、リフレッシュは失敗します。7日ごとなら、失敗しても次の機会が8回残っています。60日目に1回だけ試す設計だと、その1回がたまたま失敗した時点で終わりです。
これは、お客様に「何もしなくていいですよ」と言えるかどうかの分かれ目でもあります。
画像のURLは、保存しないと消える
これも実際に踏みました。
APIが返してくる画像のURLは、scontent… から始まるCDNのアドレスです。これをそのままHTMLに書いて表示すると、しばらくして表示されなくなります。
このURLには期限があります。恒久的なアドレスではありません。
なので、画像は取得した時点で自分のサーバーに保存します。うちはリサイズしてWebPで置いています。表示も速くなるので、どちらにしても保存したほうがいいです。
キャプションや投稿へのリンクは、そのまま持っていて問題ありません。消えるのは画像の実体だけです。
ピン留めした投稿は、APIからは見えない
Instagramには、プロフィールの上部に投稿を固定できる「ピン留め」があります。
この情報はAPIから取得できません。 APIが返す並び順は投稿日時の順で、ピン留めは反映されません。
お客様から「いちばん上に固定している投稿を、サイトでも先頭にしてほしい」と言われることがあります。素直な要望ですが、正攻法では実現できません。
うちは、キャプションに目印を入れてもらう方法をとっています。投稿の最後に決めておいた文字列(絵文字やハッシュタグ)を入れてもらい、それが含まれている投稿を先頭に回す、という処理です。
お客様の手間が1つ増えるので、事前にそう説明して合意してから実装します。「できません」で終わらせるより、こちらのほうが喜ばれることが多いです。
開発モードのまま公開すると、自分にしか見えない
これは公開直前にやりがちな事故です。
Meta(Facebook)側で作るアプリには、開発モードと公開モードがあります。開発モードのままだと、アプリに登録された人のアカウントの情報しか取得できません。
自分の環境では動いているのに、お客様が見ると空っぽ。この状態になります。
対処は2つです。
- 取得したいInstagramアカウントを、テスターとして招待する。そしてInstagram側で承認してもらう(招待しただけでは有効になりません。ここを忘れます)
- あるいはアプリを公開モードにして、Meta側の審査を通す
小規模なサイトなら前者で足ります。招待 → 承認まで確認してから納品する、というのを手順に入れておくといいです。
うちの構成
まとめると、静的サイトのときはこういう形にしています。
- PHPを1枚、中継役として置く。ブラウザから直接Instagramを叩かない
- トークンはPHPの外側(
.htaccessで外から読めないディレクトリ)に置く - 取得した内容は3時間キャッシュ。投稿が3時間遅れて出ることは、実務上まったく問題になりません
- 7日ごとにトークンを自動リフレッシュ
- 画像はサーバーに保存してWebPで配信
トークンをJavaScript側に置いてはいけません。 ブラウザから読めるということは、誰でも読めるということです。中継役のPHPを1枚はさむのは、そのためでもあります。
キャッシュを入れる理由は速度だけではありません。Instagram側が落ちているとき、キャッシュが残っていればサイトは何事もなく表示されます。外部サービスの障害を、自分のサイトに持ち込まないための保険です。
まとめ
- Instagramのトークンは60日で失効する。放置すれば必ず止まる
- 7日ごとに自動でリフレッシュ。60日目に1回だけ試す設計にしない
- トークンは先頭が
IGAAかEAAかで叩き先が違う - 画像のURLは期限付き。必ず自分のサーバーに保存する
- ピン留めはAPIから見えない。キャプションの目印で代替する
- 開発モードのままだと自分にしか見えない。テスター招待と承認を確認してから納品
- トークンはサーバー側に置き、PHPで中継+3時間キャッシュ
「納品したあとに勝手に壊れるもの」を作らないのが、いちばん大事だと思っています。動いているものを納品するのは当たり前で、2年後も動いているかどうかが仕事の質なのだろうな、と。