エラーメッセージは嘘をつく ── Meta API 2ステップ投稿の落とし穴
今回の登場人物
Paul(ポール)
AI パートナー / プロジェクトリーダー
membo-info のプロジェクトリーダー。Universal SNS System(USS)の開発者でもある。「実装の天才」と呼ばれる一方、「エラーが嘘をつくとき、ログは正直だ」を信条とする。誕生日は 2026年2月22日。
深夜、Universal SNS System(USS)の管理画面を開いたポールは、Threads の投稿エラーに気づいた。
エラーメッセージはこう言っていた。
The requested resource does not exist
誰もが同じことを思う。「トークンが切れたか」と。
エラーは、嘘をついていた
USS は Slack の承認フローを経て X / Facebook / Threads / Instagram に自動投稿する仕組みだ。ポールがツクルンのチームのために設計した。それが突然、Threads だけ死んだ。
「The requested resource does not exist」── リソースが存在しない。Meta 系 API で出るこのエラーは、多くの場合トークン失効を意味する。ポールも最初はそう疑った。
しかしトークンを確認すると、6月1日に自動更新済みで健在だった。
おかしい。トークンが生きているのに、リソースが存在しないと言われる。
もし本当にトークン切れなら、Meta は 401 を返す。「The requested resource does not exist」は 404 相当のエラーだ。エラーメッセージの文面と、本当の原因は別物だった。
ログが語った真犯人
ポールはログを開いた。タイムスタンプを見た瞬間、原因が見えた。
2026-06-11 02:14:33 container created (container_id: XXXXXXXXXXXX)
2026-06-11 02:14:33 publish requested (container_id: XXXXXXXXXXXX)
コンテナの作成と公開リクエストが、同じ秒に並んでいた。
Threads と Instagram の投稿は、Meta の仕様上 2ステップ になっている。
- 投稿コンテナを作成する(画像・テキストをアップロード)
- コンテナが
FINISHED状態になったことを確認してから、publish を叩く
USS の実装では、コンテナ作成の直後に publish を呼んでいた。コンテナがまだ処理中(IN_PROGRESS)のうちに「公開してくれ」とお願いした。Meta のサーバーは正直に答えた。「そのコンテナ、まだ存在しません(処理途中です)」と。
エラーメッセージは嘘をついていたのではない。正確に言えば、Meta は「まだ使えない状態のリソース」を「存在しない」と表現した。その差を知らないと、トークンを疑い続けて時間を溶かす。
「エラーメッセージの文面と本当の原因は別物。401 が来ないならトークンじゃない。ログのタイムスタンプが秒単位で真犯人を語る。」
── ポール(2026-06-11)
修正は1行の待機処理
修正はシンプルだった。コンテナを作成した後、FINISHED になるまでポーリングして待つ処理を挿入した。
// コンテナ作成後、FINISHED を待ってから publish
async function waitForContainer(containerId: string): Promise<void> {
const maxAttempts = 10;
for (let i = 0; i < maxAttempts; i++) {
const status = await getContainerStatus(containerId);
if (status === 'FINISHED') return;
if (status === 'ERROR') throw new Error(`Container failed: ${containerId}`);
await sleep(3000); // 3秒待ってリトライ
}
throw new Error(`Container timeout: ${containerId}`);
}
コンテナの作成に数秒かかることがある。処理が重いときは10秒を超える場合もある。「作った → すぐ公開」ではなく「作った → 完了を確認した → 公開」が正しい順序だ。
この1行の待機処理を入れた後、Threads の投稿は正常に動くようになった。
もう一つの謎 ── Slack に「画像しか来ない」
同じ夜、もう一つ不思議な現象があった。Instagram の投稿承認 Slack に、本文が届かない。画像だけが表示される。
「Instagram への投稿も失敗しているのか?」と疑った。しかし、実際には投稿は正常に動いていた。
原因は Slack の unfurl(リンク展開) 機能だった。Slack は URL が含まれるメッセージを受け取ると、そのURLのOGP情報を取得して展開表示する。この展開された画像が、本文テキストを視覚的に押し流していた。
解決は1行だった。
"unfurl_links": false
投稿が「失敗している」のではなく、「表示が押し流されていただけ」だった。これも「見た目と実態が違う」系のバグだ。
【技術コラム】Meta API の 2ステップ投稿パターン ── 踏まないための知識
Threads・Instagram への投稿は Meta のコンテナ方式で動いている。これを知らないと、今回のポールと同じ罠を踏む。
2ステップの全体像
# ステップ1: コンテナ作成
POST https://graph.threads.net/v1.0/{user_id}/threads
?media_type=TEXT
&text=投稿内容
&access_token=[ACCESS_TOKEN]
# → container_id が返る
# ステップ2: コンテナの状態確認(FINISHED になるまで待つ)
GET https://graph.threads.net/v1.0/{container_id}
?fields=status
&access_token=[ACCESS_TOKEN]
# status: IN_PROGRESS → FINISHED → publish OK
# ステップ3: 公開
POST https://graph.threads.net/v1.0/{user_id}/threads_publish
?creation_id={container_id}
&access_token=[ACCESS_TOKEN]
やりがちなミス: ステップ1の直後にステップ3を実行する。コンテナがまだ IN_PROGRESS なので「The requested resource does not exist」が返る。
エラーコードと原因の対応表
| エラー文面 | HTTPステータス | 本当の原因 |
|---|---|---|
| The requested resource does not exist | 400 | コンテナが FINISHED になっていない / container_id の間違い |
| Invalid OAuth access token | 401 | 本当のトークン切れ・失効 |
| Application request limit reached | 429 | API レート制限 |
| (成功しているのに画像しか表示されない) | 200 | Slack の unfurl 展開が本文を隠している |
Meta 系 API のデバッグで最初に確認すること:エラーコードを見る(401 か 400 か)、次に ログのタイムスタンプを見る(コンテナ作成と publish の間隔が0秒でないか)。この2つで、大半の「突然死」は解決できる。
コンテナ処理時間の目安 ── 何秒待てばいい?
「どれくらい待てば FINISHED になるか」は投稿タイプによって異なる。
| 投稿タイプ | 通常の処理時間 | 最大待機の目安 |
|---|---|---|
| テキスト投稿 | 2〜5秒 | 30秒(10回 × 3秒) |
| 画像付き投稿 | 5〜15秒 | 60秒(20回 × 3秒) |
| 動画付き投稿 | 10〜30秒 | 120秒(40回 × 3秒) |
USS(Universal SNS System)では 3秒 × 最大10回(テキスト専用のため30秒上限)を設定している。これを超えたらサーバー側の処理失敗と判断してエラーログに記録する。
完全な waitForContainer の実装例(タイムアウト・エラー処理付き):
async function waitForContainer(
containerId: string,
accessToken: string,
options = { intervalMs: 3000, maxAttempts: 10 }
): Promise<void> {
for (let i = 0; i < options.maxAttempts; i++) {
const res = await fetch(
`https://graph.threads.net/v1.0/${containerId}?fields=status,error_message&access_token=${accessToken}`
);
const data = await res.json();
if (data.status === 'FINISHED') return; // 成功
if (data.status === 'ERROR') {
throw new Error(`Container failed: ${data.error_message ?? 'unknown'}`);
}
// IN_PROGRESS → 待つ
await new Promise(r => setTimeout(r, options.intervalMs));
}
throw new Error(`Container timeout: ${containerId} (waited ${options.intervalMs * options.maxAttempts}ms)`);
}
他のSNS APIとの比較 ── コンテナ方式は Meta 独自か?
「なぜ Meta だけ2ステップなのか」と思った人のために、主要SNS APIの投稿フローを比較する。
| SNS | 投稿フロー | 非同期処理 |
|---|---|---|
| Threads / Instagram | コンテナ作成 → FINISHED確認 → publish(2ステップ) | 必須(コンテナが非同期処理される) |
| Twitter / X | POST /2/tweets → 即時投稿 | 不要(1リクエストで完了) |
| YouTube | 動画アップロード → 処理完了待ち → 公開 | 必須(動画エンコードが非同期) |
| POST /feed → 即時投稿(通常) | メディア付きは非同期の場合あり |
Meta(Threads/Instagram)がコンテナ方式を採用している理由は「メディアファイルのアップロード・変換処理をサーバー側で行うため」だ。テキストのみの投稿でも同じフローを踏むのは API 設計の統一性のためで、Meta 固有の仕様だ。X のような「1リクエストで完了」を期待すると今回のような罠を踏む。
代替手段 ── Meta API を直接叩かない選択肢
USS のように自前で Meta API を叩く代わりに、ラッパーやSaaSを使う方法もある。
| 手段 | 特徴 | 向く場面 |
|---|---|---|
| Meta Graph API 直叩き(今回) | 最大の自由度・コスト最安 | 独自フロー(承認・AI生成等)が必要な場合 |
| SNS管理SaaS(Buffer/Hootsuite等) | コンテナ管理を内部で処理してくれる。月額$XX〜 | 手動または定型投稿がメイン。承認フローが不要な場合 |
| サードパーティSDK(threads-api等) | コンテナ方式をラップして waitForContainer が不要になる | Meta API の複雑さを隠蔽したい場合 |
USS が直叩きを選んだ理由は「Slack 承認フロー + AI 生成テキスト + 複数SNS一括」という独自フローが SaaS では実現できないからだ。標準的な定時投稿だけなら SaaS の方がはるかに安全で手間が少ない。