2026年6月17日

2026年6月17日

WordPressのREST APIエラーを解決する方法

はじめに

WordPressのREST APIが403 Forbidden401 Unauthorized404 Not Foundになる問題は、パーマリンク設定・認証トークンの不備・プラグインによるブロックが原因であることが多いです。

症状・原因

  • /wp-json/wp/v2/postsにアクセスすると403または404になる
  • JavaScript から fetch した REST API が認証エラーになる
  • カスタムエンドポイントに登録したのに404になる
  • REST APIを無効化するプラグインが競合している

解決手順

ステップ1:REST APIの状態を確認する

# REST APIのルートインデックスを確認
curl -s https://example.com/wp-json/ | jq '.namespaces'

# wp/v2エンドポイントを確認
curl -s https://example.com/wp-json/wp/v2/posts?per_page=1 | jq '.[0].id'

# 認証なしでアクセスできるか確認
curl -I https://example.com/wp-json/wp/v2/users

# WP-CLIでREST APIをテスト
wp eval "
\$response = rest_do_request(new WP_REST_Request('GET', '/wp/v2/posts'));
echo 'Status: ' . \$response->get_status() . PHP_EOL;
echo 'Count: ' . count(\$response->get_data()) . PHP_EOL;
"

ステップ2:パーマリンクと基本設定を修正する

# パーマリンクをリセット(REST APIのルートも再生成される)
wp rewrite flush --hard

# REST APIが有効かオプションを確認
wp option get permalink_structure
# 空の場合はパーマリンクが「基本」設定になっており REST API が動かない

# パーマリンクを投稿名に変更
wp option update permalink_structure '/%postname%/'
wp rewrite flush --hard
// functions.php: REST APIを誤って無効化しているコードを削除する
// 以下のようなコードがあれば削除
// add_filter('rest_authentication_errors', function() { return new WP_Error('disabled', '...'); });

// REST APIへのアクセスを特定ユーザーに制限する(正しい方法)
add_filter('rest_authentication_errors', function(mixed $result): mixed {
    if (!is_user_logged_in() && !empty($result)) {
        return $result;
    }
    // 公開エンドポイントはそのまま許可
    return $result;
});

ステップ3:カスタムエンドポイントを正しく登録する

// functions.php: カスタム REST API エンドポイントを登録
add_action('rest_api_init', function(): void {
    register_rest_route('my-plugin/v1', '/items', [
        'methods'             => WP_REST_Server::READABLE, // GET
        'callback'            => 'my_get_items_callback',
        'permission_callback' => '__return_true',          // 認証不要
        'args'                => [
            'per_page' => [
                'type'              => 'integer',
                'default'           => 10,
                'minimum'           => 1,
                'maximum'           => 100,
                'sanitize_callback' => 'absint',
            ],
        ],
    ]);

    register_rest_route('my-plugin/v1', '/items/(?P<id>\d+)', [
        [
            'methods'             => WP_REST_Server::READABLE,
            'callback'            => 'my_get_item_callback',
            'permission_callback' => '__return_true',
            'args'                => [
                'id' => ['validate_callback' => fn($v) => is_numeric($v)],
            ],
        ],
        [
            'methods'             => WP_REST_Server::EDITABLE, // PUT/PATCH
            'callback'            => 'my_update_item_callback',
            'permission_callback' => fn() => current_user_can('edit_posts'),
        ],
    ]);
});

function my_get_items_callback(WP_REST_Request $request): WP_REST_Response {
    $per_page = $request->get_param('per_page') ?? 10;
    $posts = get_posts(['posts_per_page' => $per_page, 'post_status' => 'publish']);

    return new WP_REST_Response(array_map(function(WP_Post $post): array {
        return [
            'id'    => $post->ID,
            'title' => $post->post_title,
            'url'   => get_permalink($post->ID),
        ];
    }, $posts), 200);
}

ステップ4:JavaScriptからの認証(nonce)を正しく設定する

// functions.php: フロントエンドのJavaScriptにnonceを渡す
add_action('wp_enqueue_scripts', function(): void {
    wp_enqueue_script(
        'my-app',
        get_template_directory_uri() . '/js/app.js',
        ['wp-api-fetch'],
        filemtime(get_template_directory() . '/js/app.js'),
        true
    );

    wp_localize_script('my-app', 'myApp', [
        'apiUrl' => esc_url_raw(rest_url()),
        'nonce'  => wp_create_nonce('wp_rest'),
    ]);
});
// js/app.js: WordPress REST API への認証リクエスト
// ① wp-api-fetch を使う(推奨)
wp.apiFetch.use(wp.apiFetch.createNonceMiddleware(myApp.nonce));

wp.apiFetch({ path: '/wp/v2/posts?per_page=5' })
    .then(posts => console.log(posts))
    .catch(err => console.error(err));

// ② fetch を直接使う場合
fetch(`${myApp.apiUrl}wp/v2/posts`, {
    headers: {
        'X-WP-Nonce': myApp.nonce,
        'Content-Type': 'application/json',
    },
})
.then(res => res.json())
.then(data => console.log(data));

ステップ5:CORSエラーと外部からのアクセスを許可する

// functions.php: REST APIにCORSヘッダーを追加
add_action('rest_api_init', function(): void {
    remove_filter('rest_pre_serve_request', 'rest_send_cors_headers');

    add_filter('rest_pre_serve_request', function(mixed $value): mixed {
        $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
        $allowed = ['https://app.example.com', 'https://admin.example.com'];

        if (in_array($origin, $allowed, true)) {
            header('Access-Control-Allow-Origin: ' . $origin);
            header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
            header('Access-Control-Allow-Credentials: true');
            header('Access-Control-Allow-Headers: Authorization, Content-Type, X-WP-Nonce');
        }
        return $value;
    });
});

// Application Password を使った外部からの認証(WordPress 5.6以降)
// Authorization: Basic base64(username:application_password) ヘッダーを付与
# CORSエラーをcurlで確認
curl -I -H "Origin: https://app.example.com" \
    https://example.com/wp-json/wp/v2/posts

# Application Password での認証テスト
curl -u "username:xxxx xxxx xxxx xxxx xxxx xxxx" \
    https://example.com/wp-json/wp/v2/users/me

注意事項

  • permission_callback'__return_true'を設定すると認証なしでアクセスできます。機密データを返すエンドポイントでは必ずcurrent_user_can()等で権限チェックを行ってください
  • REST APIを完全に無効化するプラグイン(Disable REST API等)がインストールされていると、他のプラグインやGutenbergが動作しなくなります。無効化するより認証ベースのアクセス制限を推奨します
  • Application Passwordはアプリケーションパスワード(スペース区切り24文字)をBasic認証で使用します。通常のログインパスワードとは別に管理画面の「ユーザー → プロフィール」から発行してください

まとめ

REST APIエラーの解決は①curlでエンドポイントにアクセスしてHTTPステータスを確認、②wp rewrite flush --hardでパーマリンクをリセットしREST APIルートを再生成、③register_rest_route()rest_api_initフックに正しくエンドポイントを登録しpermission_callbackを設定、④wp_create_nonce('wp_rest')で生成したnonceをX-WP-Nonceヘッダーに付与してJS認証、⑤外部サービスからのアクセスはCORSヘッダー追加またはApplication Passwordで対応します。

お気軽にご相談ください

お見積りへ お問い合わせへ