WordPress REST APIで401・404・403に詰まった話。AI生成記事の下書き投稿で確認したこと

はじめに

前回の記事では、品質チェック済みのAI生成記事を、WordPress REST APIで下書き登録する仕組みについて書いた。

筆者のブログ自動化ツールでは、記事構成案を作り、OpenAI APIで本文下書きを生成し、品質チェックを行い、問題なさそうなものだけWordPressへ下書き登録する流れにしている。

ここで使ったのが、WordPress REST APIである。

ただし、実装は一発では通らなかった。

実際には、次のようなエラーに順番に遭遇した。

401 Unauthorized
404 Not Found
403 Forbidden

それぞれ原因が違う。

しかも、同じ403でも「認証が通っていない403」と「本文がWAFに弾かれている403」では意味が違う。

本記事では、筆者がWordPress REST APIでAI生成記事を下書き投稿する際に詰まったエラーと、確認したポイントを整理する。

最初に確認した前提

今回やりたかったことは、WordPressへ記事を自動公開することではない。

目的は、AIで生成した本文をWordPressに下書きとして登録することである。

投稿ステータスは必ず draft とした。

status = draft

公開はWordPress管理画面で人間が確認してから行う。

また、WordPress REST APIの認証にはApplication Passwordを使った。

.env には、以下のような設定を入れる。

WORDPRESS_BASE_URL=https://syanaise3wariup.com
WORDPRESS_USERNAME=your_wordpress_username
WORDPRESS_APPLICATION_PASSWORD="your_application_password"
WORDPRESS_REST_API_MODE=rest_route
WORDPRESS_POST_BODY_MODE=form
WORDPRESS_WAF_SAFE_MODE=true

ここで使うパスワードは、WordPressの通常ログインパスワードではない。
WordPressのプロフィール画面で発行するApplication Passwordである。

この前提で、WordPress REST APIへ接続しようとしたところ、いくつかのエラーに遭遇した。

まずは小さくテストすることが重要

WordPress REST APIで投稿機能を作るとき、いきなり実記事を投稿しない方がよい。

筆者は、以下の順番で切り分けることにした。

1. REST APIのエンドポイントが見えるか
2. Application Password認証が通るか
3. 最小のテスト投稿ができるか
4. 実際の記事本文を投稿できるか

この順番が重要である。

いきなり実記事を送ってエラーになると、原因が分からない。

認証が悪いのか、REST APIのURLが違うのか、本文がWAFに弾かれているのか、切り分けが難しくなる。

そのため、最初は短い本文でテスト投稿する。

タイトル: API投稿テスト
本文: これはREST API投稿テストです。
ステータス: draft

これが通るかどうかで、その後の判断が変わる。

401 Unauthorized: Application Password認証が通らない

最初に詰まったのが、401エラーである。

/wp/v2/users/me にアクセスして認証確認をしたところ、401が返ってきた。

401は、ざっくり言えば「ログイン済みユーザーとして認識されていない」という状態である。

このとき確認したのは以下である。

  • WordPressのユーザー名が正しいか
  • 表示名ではなくログイン用ユーザー名を使っているか
  • 通常のログインパスワードではなくApplication Passwordを使っているか
  • Application Passwordの空白や改行が混ざっていないか
  • Application Passwordを作成したユーザーと、.env のユーザー名が一致しているか

Application Passwordは、発行時に空白区切りで表示されることがある。

たとえば、以下のような形式である。

abcd efgh ijkl mnop qrst uvwx

この空白を含めるべきか、除去するべきかで迷うことがある。

筆者の環境では、パスワードそのものを確認し直し、必要に応じて再発行した。

ただ、それでも認証が通らない場合がある。

Authorizationヘッダーが届いていない可能性

ユーザー名もApplication Passwordも合っているのに401になる場合、AuthorizationヘッダーがWordPressまで届いていない可能性がある。

Pythonの requests でBasic認証を使うと、Authorizationヘッダーが送られる。

しかし、サーバー構成やWordPressの設定によっては、そのヘッダーがPHPやWordPress側へ渡らないことがある。

この場合、WordPressから見ると「認証情報が送られていない」ように見える。

筆者もこの可能性を疑った。

確認したポイントは以下である。

  • WordPressのサイトヘルスにAuthorization headerの警告が出ていないか
  • .htaccess でAuthorizationヘッダーを渡す設定が必要ではないか
  • セキュリティプラグインがREST API認証を止めていないか
  • サーバー側WAFやセキュリティ設定がBasic認証を遮断していないか

ただし、今回の環境では最終的に、パーマリンク設定を変更・保存することで認証が通るようになった。

パーマリンク設定を見直したら認証が通った

今回、認証まわりで最終的に効いたのが、WordPressのパーマリンク設定である。

WordPress管理画面で、以下を確認した。

設定
↓
パーマリンク

この設定を見直し、保存し直すことで、REST APIの認証が通るようになった。

パーマリンク設定は、REST APIのルーティングにも関係することがある。

そのため、Application Password認証が通らない場合でも、単純にパスワードだけを疑うのではなく、パーマリンク設定やREST APIのルーティングも確認した方がよい。

WordPress REST APIのエラーは、見た目より奥が深い。

401だからといって、必ずしもパスワード間違いとは限らない。

404 Not Found: /wp-json/形式でREST APIが見つからない

認証が通るようになったあと、次に遭遇したのが404である。

一般的にWordPress REST APIは、次のようなURLでアクセスする。

https://example.com/wp-json/wp/v2/posts

しかし、筆者の環境では、この /wp-json/ 形式で404が返ることがあった。

このとき返ってきたのは、WordPress REST APIのJSONエラーではなく、HTMLの404ページである。

これは重要な違いである。

REST APIのJSONエラーならWordPress側の応答である可能性が高い。
一方、HTMLの404ページなら、WordPress REST APIのルートとして処理されていない可能性がある。

そこで、?rest_route= 形式を試した。

https://syanaise3wariup.com/?rest_route=/wp/v2/posts

この形式では通るようになった。

?rest_route=形式に対応させた

今回のツールでは、REST APIのURL形式を設定で切り替えられるようにした。

.env には以下を設定する。

WORDPRESS_REST_API_MODE=rest_route

対応するモードは以下である。

wp_json
rest_route
auto

wp_json の場合は、以下の形式を使う。

https://example.com/wp-json/wp/v2/posts

rest_route の場合は、以下の形式を使う。

https://example.com/?rest_route=/wp/v2/posts

auto の場合は、どちらが使えるか自動判定する。

今回の環境では、rest_route 形式を使うことで投稿できるようになった。

WordPress REST APIを使う場合、/wp-json/ 形式だけを前提にすると詰まることがある。
特にレンタルサーバーやパーマリンク設定の影響を受ける環境では、?rest_route= 形式も試す価値がある。

最小テスト投稿は成功した

認証とREST APIのURL形式を修正したあと、まず短い本文でテスト投稿した。

テスト内容は以下である。

タイトル: API投稿テスト
本文: これはREST API投稿テストです。
ステータス: draft

この最小投稿は成功した。

ここで分かったことは以下である。

Application Password認証は通っている
REST APIのURL形式も正しい
WordPressへdraft投稿できる

ここまで通れば、基本的なREST API接続は成功である。

しかし、実際の記事下書きを投稿すると、また別の問題が出た。

403 Forbidden: 実記事だけ投稿できない

最小テスト投稿は成功した。
しかし、実際のAI生成記事を投稿すると403になった。

ここがややこしいところである。

認証は通っている。
REST APIのURLも正しい。
短い本文なら投稿できる。

それなのに、実記事だけ403になる。

この場合、疑うべきは本文の中身である。

技術記事には、次のようなものが含まれやすい。

  • PowerShellコマンド
  • Pythonコード
  • Windowsパス
  • JSON
  • YAML
  • HTMLタグ
  • URL
  • Markdownのコードブロック

これらが、サーバー側のWAFに攻撃っぽく見えることがある。

今回の403も、WordPress REST APIのJSONエラーではなく、HTMLの403ページだった。
つまり、WordPress本体というより、サーバー側のWAFやセキュリティ機能に弾かれている可能性が高いと考えた。

403は「認証失敗」とは限らない

403というと、権限不足を連想しやすい。

しかし、今回のように実記事だけ403になる場合は、認証や権限とは別の可能性がある。

切り分けとしては、以下を見るとよい。

/users/me は成功するか
最小テスト投稿は成功するか
短い本文なら投稿できるか
実記事だけ失敗するか
レスポンスはJSONかHTMLか

今回のように、認証確認も最小投稿も成功しているなら、REST APIそのものは動いている。

そのうえで実記事だけ403なら、本文内容がWAFに引っかかっている可能性が高い。

これは、コードだけではなくサーバー側の挙動も関係する。

JSON送信からform送信に切り替えた

WAF対策として、まず投稿時の送信方式を切り替えられるようにした。

最初はJSONで送っていた。

requests.post(url, auth=(user, password), json=payload)

これを、フォーム送信に切り替えられるようにした。

requests.post(url, auth=(user, password), data=payload)

.env には以下を設定した。

WORDPRESS_POST_BODY_MODE=form

環境によっては、JSON本文の方がWAFに強く見られることがある。

form送信に切り替えることで通る場合があるため、設定で変えられるようにした。

こうした回避策は、環境依存である。
そのため、コードに固定するより、.env で切り替えられるようにした方が扱いやすい。

WAF安全モードを追加した

それでも実記事が弾かれる場合に備えて、WAF安全モードも追加した。

WORDPRESS_WAF_SAFE_MODE=true

WAF安全モードでは、WordPressへ送る本文だけを安全化する。

主な処理は以下である。

  • コードブロックをプレースホルダー化する
  • 長いコマンド列をそのまま送らない
  • Windowsパスや記号の多い行を抑える
  • 危険視されやすいHTMLタグを除去する
  • 本文末尾に、詳細コマンドは管理画面で確認・追記する旨を入れる

重要なのは、元のMarkdownファイルは変更しないことである。

あくまでWordPressへ送るpayloadだけを安全化する。

これにより、記事本文がWAFに弾かれるリスクを下げられる。

ただし、WAF安全モードを使った場合、WordPress管理画面で本文を確認し、必要に応じてコードブロックを手動で整える必要がある。

完全自動ではなく、安全に下書き登録するための妥協点である。

本当の原因はPHPの形式だった

WAFの安全モード云々言いながら、実はこれでは403は解消しなかった。

これは何度chatGPTやGeminiに聞いても分からなくて、結局は昔ながらの検索で調べたのだが、1件だけ同じようなケースに言及しているサイトがあった。

そこで紹介されていたのが、レンタルサーバのPHPがCGI版だとエラーになるというものだった。

試しに確認してみると確かにこのサイトはCGI版で動いていた。

これをモジュール版にすれば良いとのことなので変更してみると、想定通り403のエラーが出なくなった。

AIに聞いて何でも分かるような気がしていたが、レンタルサーバの設定という視点はなく、検索して初めて気づいた観点であった。

ログを見て切り分ける

今回のエラー対応では、ログがかなり役に立った。

投稿処理では、以下をログに出すようにした。

  • 実行モード
  • 対象候補数
  • REST APIのURL
  • 認証テストの成否
  • 投稿対象ファイル
  • エラーステータス
  • レスポンス先頭部分
  • 結果CSVの保存先

たとえば、以下のような情報があると、状況を把握しやすい。

requested_mode=rest_route
resolved_mode=rest_route
users_me_url=https://example.com/?rest_route=/wp/v2/users/me
posts_url=https://example.com/?rest_route=/wp/v2/posts
WordPress auth test succeeded
WordPress API error 403

このログから、認証とREST APIのURLは成功しているが、投稿本文で403になっていると判断できる。

自動化ツールでは、エラーが出ること自体よりも、どこで失敗したか分からないことの方がつらい。

ログは、暗い廊下に置く小さなランタンである。

Codexには診断より修正を任せた

今回、REST APIの接続診断は人間側で行った。

Codexには、どちらかというと修正や機能追加を任せた。

たとえば、以下のような対応である。

REST API URL形式を切り替えられるようにする
post body modeをjson/formで切り替えられるようにする
WAF安全モードを追加する
403時のエラーメッセージを分かりやすくする
投稿結果をCSVとMarkdownレポートに残す

REST APIの接続確認は、実際のサーバー環境やWordPress設定を見ながら切り分ける必要がある。

そのため、Codexには「このエラーの原因を完全に診断して」と頼むより、こちらで切り分けた結果をもとに、必要な修正を実装させる方が進めやすかった。

Codexは、現場のサーバー設定そのものは見られない。
しかし、こちらが原因の方向性を伝えれば、設定切り替えや例外処理の実装はかなり役に立つ。

エラー対応で分かったこと

今回のWordPress REST API連携で分かったことは、エラーの数字だけ見ても判断できないということである。

同じ401でも、原因は単純なパスワードミスとは限らない。

同じ404でも、記事が存在しないのではなく、REST APIのルーティングが効いていない可能性がある。

同じ403でも、権限不足ではなく、WAFが本文を弾いている可能性がある。

重要なのは、段階的に確認することである。

GETでREST APIが見えるか
/users/meで認証が通るか
最小POSTが通るか
実記事POSTが通るか

この順番で確認すれば、原因をかなり絞り込める。

自動化では外部書き込み処理を慎重に扱う

今回のWordPress下書き投稿は、外部サービスへ書き込む処理である。

このような処理は、慎重に扱う必要がある。

筆者は以下を徹底した。

dry-runを用意する
executeを明示した時だけ投稿する
status=draftに固定する
最初は1件だけ投稿する
投稿結果をログに残す
エラー時はHTMLレスポンスの先頭を記録する

特に重要なのは、status=draft である。

エラー対応中に自動公開まで行っていたら、かなり危険だった。
下書き投稿だけにしていたからこそ、安心して試せた。

AI生成記事をWordPressに送る場合、最初に作るべきなのは公開機能ではない。
安全な下書き登録機能である。

まとめ

筆者は、WordPress REST APIでAI生成記事を下書き投稿する仕組みを作る中で、401、404、403のエラーに遭遇した。

401では、Application Password認証、ユーザー名、Authorizationヘッダー、パーマリンク設定を確認した。

404では、/wp-json/ 形式だけでなく、?rest_route= 形式を試した。

403では、認証や最小投稿が通っているかを確認し、実記事だけ失敗する場合はWAFによる本文ブロックを疑った。

最終的には、WORDPRESS_REST_API_MODE=rest_routeWORDPRESS_POST_BODY_MODE=formWORDPRESS_WAF_SAFE_MODE=true などの設定を用意し、環境に合わせて切り替えられるようにした。

WordPress REST API連携は、コードだけで完結しない。
WordPress設定、パーマリンク、サーバーWAF、セキュリティプラグインなども関係する。

だからこそ、段階的な切り分けとログ出力が重要である。

AI生成記事をWordPressへ送る場合でも、自動公開はせず、まずは安全に下書き登録する。

この設計にしておいたことで、エラー対応もしやすくなった。

次回は、Python自動化ツールで .env.env.example を分ける理由や、GitHubに上げてはいけないファイルについて整理する予定である。

コメント

タイトルとURLをコピーしました