WordPress REST API × X API 連携を実装してみた

導入

WordPress ブログの投稿を自動的に X (旧 Twitter) にシェアしたい。そう考える管理者は多いですが、実装方法となると選択肢が分かれます。プラグイン頼みにするか、API を直接叩くか。

今回は、WordPress REST API と X API を直結させ、ブログ更新時に自動投稿する仕組みを実際に構築してみました。その過程で見えた課題、実装パターン、そして意外な落とし穴について解説します。

結論から書きます

WordPress の REST API と X API を組み合わせることで、プラグイン不要な自動シェア機能が実装できます。ただし認証周りと API レート制限への対策が必須です。本記事では検証環境での実装フロー、トラブルシューティング、そして本番運用への注意点をまとめました。

仮説と目的

仮説

WordPress の REST API フックと X API の OAuth 2.0 認証を組み合わせれば、カスタムコード(プラグイン化)で自動シェアが実装でき、サードパーティプラグインの依存を減らせるのではないか。

目的

  1. 新規投稿時に自動的に X へ投稿を通知
  2. API 認証フローと実装パターンの確認
  3. レート制限・エラーハンドリングの現実的な対応法を探る

検証環境・前提条件

環境情報

  • WordPress 6.4.3 (最新安定版)
  • X API v2 (Elevated Access を想定)
  • PHP 8.1 以上
  • wp-cli 導入済み
  • 検証日時: 2025年 5 月実施

前提

  • X Developer Portal でのアプリ登録完了
  • API キーと Bearer Token の取得済み
  • WordPress のテーマまたはカスタムプラグインにコードを記述可能な環境

実装の基本フロー

WordPress REST API で投稿フックを捕捉

まず WordPress 側で、新規投稿の作成・更新時に何かアクションを起動する必要があります。REST API 経由で投稿が作られる場合、rest_insert_post フックが発火します。

// functions.php または custom-plugin.php
add_action('rest_insert_post', 'share_to_x_api', 10, 3);

function share_to_x_api($post, $request, $creating) {
    // 新規作成時のみ実行
    if (!$creating) {
        return;
    }

    // 特定のカテゴリやステータスのみ対象にする
    if ($post->post_status !== 'publish') {
        return;
    }

    // X API へ通知を送る
    send_to_x($post);
}

ここで重要なのは、REST API 経由での作成のみ を想定していることです。WordPress の管理画面から投稿した場合は、このフックが発火しません(その場合は publish_post などの別フックを使う必要があります)。

X API への投稿送信

次に、X API v2 の POST /tweets エンドポイントを叩きます。認証は Bearer Token を使う方式です。

function send_to_x($post) {
    $post_title = $post->post_title;
    $post_url = get_permalink($post->ID);

    // X に投稿するテキスト(280字以内)
    $tweet_text = sprintf(
        "📝 新しい記事を公開しました\n\n%s\n\n%s",
        $post_title,
        $post_url
    );

    // 280字超過チェック
    if (mb_strlen($tweet_text) > 280) {
        $tweet_text = mb_substr($post_title, 0, 50) . "...\n\n" . $post_url;
    }

    $x_api_url = 'https://api.twitter.com/2/tweets';
    $bearer_token = get_option('x_api_bearer_token'); // 環境変数から取得推奨

    $response = wp_remote_post($x_api_url, [
        'headers' => [
            'Authorization' => 'Bearer ' . $bearer_token,
            'Content-Type' => 'application/json',
        ],
        'body' => wp_json_encode([
            'text' => $tweet_text,
        ]),
        'timeout' => 10,
    ]);

    if (is_wp_error($response)) {
        error_log('X API Error: ' . $response->get_error_message());
        return false;
    }

    $status_code = wp_remote_retrieve_response_code($response);
    if ($status_code !== 201) {
        $body = wp_remote_retrieve_body($response);
        error_log('X API returned ' . $status_code . ': ' . $body);
        return false;
    }

    return true;
}

チェックポイント

  • Bearer Token は環境変数またはセキュアなオプションテーブルに保管
  • wp_remote_post() で HTTP リクエストを送信(curl よりも WordPress 標準)
  • レスポンスコード 201 で成功判定

認証トークンの管理

Bearer Token を直接コードに埋め込むのは危険です。以下の方法を推奨します。

方法 1: .env ファイル(WP-CLI や Composer 環境で推奨)

X_API_BEARER_TOKEN=AAAABNRjWwEBBA...

functions.php で読み込み:

if (file_exists(__DIR__ . '/.env')) {
    $env_vars = parse_ini_file(__DIR__ . '/.env');
    define('X_API_BEARER_TOKEN', $env_vars['X_API_BEARER_TOKEN'] ?? '');
}

方法 2: wp-config.php に定義

define('X_API_BEARER_TOKEN', getenv('X_API_BEARER_TOKEN'));

方法 3: WordPress オプションテーブル(管理画面から変更可能)

update_option('x_api_bearer_token', 'AAAA...', false);
$token = get_option('x_api_bearer_token');

方法 3 はセキュリティリスクがあるため、本番環境では環境変数推奨です。

レート制限への対応

X API v2 には レート制限があります。通常、1 ユーザー当たり 15 分間に 300 リクエストです。ブログ 1 つ程度なら問題になりませんが、複数ドメインを運用する場合は工夫が必要です。

キューイングシステムの導入

投稿を即座に X へ送らず、一度 WordPress のメタデータに保存し、別タスクで処理する方法があります。

add_action('rest_insert_post', 'queue_x_share', 10, 3);

function queue_x_share($post, $request, $creating) {
    if (!$creating || $post->post_status !== 'publish') {
        return;
    }

    // メタデータに保存(まだ送信していない状態)
    update_post_meta($post->ID, 'x_share_pending', true);
    update_post_meta($post->ID, 'x_share_timestamp', current_time('timestamp'));
}

// 定期実行(WP-Cron または 外部 cron)
add_action('wp_scheduled_x_share', 'process_x_share_queue');

function process_x_share_queue() {
    $pending_posts = get_posts([
        'meta_key' => 'x_share_pending',
        'meta_value' => true,
        'numberposts' => 5, // 1 回の処理で 5 件まで
    ]);

    foreach ($pending_posts as $post) {
        if (send_to_x($post)) {
            delete_post_meta($post->ID, 'x_share_pending');
            update_post_meta($post->ID, 'x_share_sent', true);
        }
    }
}

// 15 分ごとに実行
if (!wp_next_scheduled('wp_scheduled_x_share')) {
    wp_schedule_event(time(), '15min', 'wp_scheduled_x_share');
}

この方式により、API レート制限に巻き込まれるリスクが低減します。

トラブルシューティング

エラー: 401 Unauthorized

原因

  • Bearer Token が空または不正
  • Token の有効期限切れ(OAuth 2.0 の場合)

対応

if ($status_code === 401) {
    error_log('X API: 認証トークン無効。トークンを再取得してください。');
    // 管理者メール通知
    wp_mail(get_option('admin_email'), 'X API 認証エラー', 'トークン更新が必要です。');
}

エラー: 429 Too Many Requests

原因

レート制限に達した。

対応

if ($status_code === 429) {
    // メタデータを再度セット(後で再試行)
    update_post_meta($post->ID, 'x_share_retry_count', 
        (int)get_post_meta($post->ID, 'x_share_retry_count', true) + 1);

    // 3 回までリトライ
    $retry_count = (int)get_post_meta($post->ID, 'x_share_retry_count', true);
    if ($retry_count < 3) {
        update_post_meta($post->ID, 'x_share_pending', true);
    }
}

エラー: 422 Unprocessable Entity

原因

POST データの形式が不正。よくは 280 字超過。

対応

テキストの事前検証を強化:

function validate_tweet_text($text) {
    $text = trim($text);

    // 絵文字を含む場合の正確な文字数カウント
    $length = mb_strlen($text);

    if ($length > 280) {
        return false; // または自動短縮
    }

    return true;
}

本番運用への注意点

セキュリティ

  • Bearer Token は環境変数で管理し、ソースコードに含めない
  • WordPress のユーザー権限で publish_posts が必要な場合、REST API エンドポイント保護を強化
  • X API アプリの権限を「Read and Write」に限定(「Read and Delete」は不要)

モニタリング

エラーログを定期確認するか、Slack / メール通知を組み込む:

function notify_x_share_failure($post_id, $error_message) {
    $post = get_post($post_id);
    $message = sprintf(
        "X API 投稿失敗\nタイトル: %s\nエラー: %s\nURL: %s",
        $post->post_title,
        $error_message,
        get_edit_post_link($post_id)
    );

    wp_mail(get_option('admin_email'), 'X API シェア失敗通知', $message);
}

投稿の選別

すべての投稿を X へシェアするのではなく、カテゴリやタグで絞る:

function should_share_to_x($post) {
    $categories = wp_get_post_categories($post->ID);
    $category_ids = wp_list_pluck(get_categories(['fields' => 'ids']), 'cat_ID');

    // 「お知らせ」カテゴリのみシェア
    return in_array($post->post_category[0], $category_ids);
}

考察:プラグイン vs カスタムコード

この検証を通じて、API 連携をカスタムコードで実装する利点と課題が明確になりました。

メリット

  • 細かいロジック制御が可能
  • 不要な機能(管理画面 UI など)が不要
  • バージョン更新時の互換性不安がない

デメリット

  • コードの保守責任が自分たちにある
  • セキュリティ検証は自分たちで行う必要がある
  • デバッグ時のログ確認が重要

結論として、運用規模が小さい場合(1-2 ドメイン)はカスタムコード、複数運用ならプラグイン化を検討 する方針が現実的です。

API レート制限とキューイングの実装詳細

レート制限の対応はブログ規模によって戦略が変わります。以下、3 つのパターンを整理しました。

パターン 想定規模 キューイング リトライ
直送 月 1-5 投稿 不要 シンプル
キュー + 定期実行 月 6-30 投稿 推奨 3 回
外部キュー(SQS など) 月 30+ 投稿 必須 カスタマイズ可

月 30 投稿程度なら、前述のキューイング方式(15 分ごと)で十分対応できます。

カスタムプラグイン化への最終手順

ここまでのコードを整理し、プラグイン化する手順を示します。

ファイル構成

wp-content/plugins/x-api-auto-share/
├── x-api-auto-share.php (メインプラグインファイル)
├── includes/
│   ├── class-x-api.php
│   ├── class-queue-manager.php
│   └── hooks.php
├── readme.txt
└── .gitignore

x-api-auto-share.php (メイン)

<?php
/**
 * Plugin Name: X API Auto Share
 * Description: REST API で投稿作成時に自動的に X へシェア
 * Version: 1.0.0
 * Author: Your Name
 * License: GPL v2
 */

if (!defined('ABSPATH')) exit;

define('X_API_SHARE_PATH', plugin_dir_path(__FILE__));

require_once X_API_SHARE_PATH . 'includes/class-x-api.php';
require_once X_API_SHARE_PATH . 'includes/class-queue-manager.php';
require_once X_API_SHARE_PATH . 'includes/hooks.php';

register_activation_hook(__FILE__, function() {
    // 定期タスク登録
    if (!wp_next_scheduled('wp_scheduled_x_share')) {
        wp_schedule_event(time(), '15min', 'wp_scheduled_x_share');
    }
});

register_deactivation_hook(__FILE__, function() {
    wp_clear_scheduled_hook('wp_scheduled_x_share');
});

includes/class-x-api.php

<?php
class X_API {
    private $bearer_token;
    private $api_url = 'https://api.twitter.com/2/tweets';

    public function __construct() {
        $this->bearer_token = getenv('X_API_BEARER_TOKEN') 
            ?: get_option('x_api_bearer_token');
    }

    public function post_tweet($text) {
        if (mb_strlen($text) > 280) {
            return new WP_Error('text_too_long', 'Tweet text exceeds 280 characters.');
        }

        $response = wp_remote_post($this->api_url, [
            'headers' => [
                'Authorization' => 'Bearer ' . $this->bearer_token,
                'Content-Type' => 'application/json',
            ],
            'body' => wp_json_encode(['text' => $text]),
            'timeout' => 10,
        ]);

        if (is_wp_error($response)) {
            return $response;
        }

        $status_code = wp_remote_retrieve_response_code($response);

        if ($status_code === 201) {
            return true;
        }

        $body = json_decode(wp_remote_retrieve_body($response), true);
        return new WP_Error('x_api_error', 
            'X API error: ' . wp_json_encode($body));
    }
}

includes/hooks.php

<?php
add_action('rest_insert_post', function($post, $request, $creating) {
    if (!$creating || $post->post_status !== 'publish') {
        return;
    }

    update_post_meta($post->ID, 'x_share_pending', true);
    update_post_meta($post->ID, 'x_share_timestamp', current_time('timestamp'));
}, 10, 3);

add_action('wp_scheduled_x_share', function() {
    $x_api = new X_API();
    $queue_mgr = new Queue_Manager();
    $queue_mgr->process($x_api);
});

このプラグイン化により、複数プロジェクトでの再利用や、GitHub での管理が可能になります。

※本記事は2026-05-16時点の情報に基づきます。AI モデルや API の仕様・料金は変更されることがあります。最新は公式ドキュメントをご確認ください。

AI / tech の選択は要件や環境によって最適解が変わります。本記事は参考情報で、最終的な技術判断はご自身の検証に基づいてください。

まとめ

  • REST API フックと X API Bearer Token で直結可能 — プラグイン不要な実装ができる
  • レート制限対策としてキューイング + 定期実行が現実的 — 月 30 投稿程度までカバー可能
  • 本番運用ではトークン管理・エラーハンドリング・モニタリングが必須 — セキュリティと安定性のため

次の段階としては、複数の X アカウント運用への対応、または Bluesky API への同時投稿を試してみたい。


Photo by 1981 Digital on Unsplash