要点: 公式 Twitter/X API からサードパーティの REST API への移行は、4つの変更を伴います。OAuth を単一の API キーヘッダーに置き換え、ベース URL を入れ替えてエンドポイントのパスを再マッピングし、レスポンスのパースをフラット化し、ページネーションを1つのカーソルフィールドに切り替えることです。読み取り側の移行は、通常1〜3日かかります。

著者:Sorsa Editorial · 2026年7月4日更新:無料スタートの経路(100回分の無料リクエスト、クレジットカード不要)を追加し、料金を1,000件あたりの単位に移し、エンドポイントのマッピングを現行の Sorsa v3 の40個のエンドポイント全体に対して再確認しました。

これは、すでに離れると決めていて、実際の移行を必要とするチームのための技術的な解説です。認証、エンドポイントのマッピング、レスポンスのパース、ページネーション、HTTP メソッド、そして動作するコードです。当社が開発・運用する Twitter/X の代替 API である Sorsa API を、全体を通じて移行先として使います。公式 v2エンドポイントからのマッピングが、利用可能なものの中で最もきれいなものの1つだからです。OAuth を1つの ApiKey ヘッダーに置き換えること、1回のバッチ呼び出しが最大100件のツイートまたはプロフィールを返すフラットなリクエスト単位の課金、すべてのプランで一律の毎秒20リクエスト、そして待つべき開発者アカウントの審査がないことが、読み取り側の移行が数週間ではなく数日で終わる実務的な理由です。読み取りは1,000ツイートあたり$0.02、1,000プロフィールあたり$0.01から動作し、100回分の無料リクエスト(クレジットカード不要、1回限り)が、コミットする前に完全な並行差分のテストパスをカバーします。まだプロバイダーを選んでいるなら、Twitter/X API の代替の比較 から始めてください。本ガイドはその決定が済んでいることを前提とします。

Sorsa は2022年以降、累計50億件を超えるリクエストを処理しながら、多くのチームのためにこの移行を代行してきたので、以下のステップはツアーではなく固定のチェックリストに従います。原則はどの読み取り専用の REST プロバイダーにも移りますが、エンドポイント名、レスポンスフィールド、コードは Sorsa 固有です。

目次


Twitter/X API からの移行には何が伴う? {#what-does-migrating-from-the-twitterx-api-involve}

公式 Twitter/X API からフラット料金の REST API への移行は、ほとんどが引き算です。OAuth を取り除き、フィールド選択の文字列を消し、レスポンスの包みを落とし、いくつかのエンドポイントのパスを再マッピングします。中核の作業は、認証、エンドポイントのパス、レスポンスのパース、ページネーションに触れ、中程度の読み取り系のコードベースは1〜3日で移ります。

段階ごとの詳細の前に、変更セット全体を一目で示します。

  • Authorization: Bearer ...ApiKey ヘッダーに置き換える。
  • ベース URL を https://api.x.com/2 から https://api.sorsa.io/v3 に入れ替える。
  • エンドポイントのパスを再マッピングする(表はステップ2)。
  • ツイートと検索のエンドポイントを GET から POST に切り替える(ステップ5)。
  • tweet.fieldsuser.fieldsexpansions を消す。すべてのフィールドがデフォルトで返る。
  • パーサーをフラット化する。dataincludesmeta の包みを取り除く。
  • フィールド名を変える。namedisplay_name に、textfull_text に、指標はトップレベルへ移る。
  • pagination_tokennext_tokennext_cursor に置き換える。
  • エラー処理を単純化する。エラーは単一の message フィールドとして返る。

以下の表は、移行に関係する差異をマッピングします。ここで重要な唯一の比較です。移行でコードがどれだけ変わるかを決めるからです。

次元公式 X API v2Sorsa API
認証OAuth 2.0 ベアラー(ユーザーコンテキストは1.0a)単一の ApiKey ヘッダー
ベース URLhttps://api.x.com/2https://api.sorsa.io/v3
フィールド選択tweet.fieldsuser.fieldsexpansionsすべてのフィールドがデフォルトで返る
レスポンスの形data / includes / meta の包みフラットなオブジェクト、作者はインライン
ページネーションpagination_token / next_tokennext_cursor
HTTP メソッド(ツイート、検索)GETPOST
バッチ限定的1回の呼び出しで最大100件のツイートまたはプロフィール
レート制限エンドポイント別、15分の窓一律の毎秒20リクエスト、全プラン
課金単位取得したリソース単位リクエスト単位
書き込みアクション投稿、DM(フォロー・いいね・引用は Enterprise のみ)読み取り専用

1つを除いて、すべての行が単純化です。その1つとは、公式 API は X に書き込めるが、フラット料金の読み取り API はできない、ということです。投稿する、DM を送る、または広告を出すなら、その経路は公式 API に残ります。公開データの読み取りには、この入れ替えが OAuth、フィールド選択の層、そしてエンドポイント別のレート窓を取り除き、それが移行の大部分です。


ステップ1:OAuth を API キーに置き換える {#step-1-replace-oauth-with-an-api-key}

認証は、最も多くのコードを取り除く変更です。公式 API は、アプリ単独のリクエストに OAuth 2.0 ベアラートークンを、ユーザーコンテキストを必要とするものに OAuth 1.0a(コンシューマーキー、アクセストークン、リクエストごとの HMAC 署名)を使います。フラット料金のプロバイダーは、そのすべてをヘッダーの中の1つのキーに置き換えます。

以前、公式 API では:

bash
curl -X GET "https://api.x.com/2/users/by/username/elonmusk" \
  -H "Authorization: Bearer AAAAAAAAAAAAAAAAAAAA..."

以後:

bash
curl -X GET "https://api.sorsa.io/v3/info?username=elonmusk" \
  -H "ApiKey: YOUR_API_KEY"

トークンの更新も、署名の生成も、コールバック URL もありません。キーを一度生成し、環境変数に保存すれば、すべてのリクエストが同じ ApiKey ヘッダーを運びます。完全なリファレンスは 認証ドキュメント にあります。


ステップ2:ベース URL を入れ替え、エンドポイントを再マッピングする {#step-2-swap-the-base-url-and-remap-endpoints}

各公式 v2 パスは、1つの新しいパスにマッピングされます。ほとんどの呼び出しは、URL だけ、そしてツイートエンドポイントでは HTTP メソッドだけが変わります。

ベース URL は https://api.sorsa.io/v3 になります。ユーザーは次のようにマッピングされます。

アクション公式 X API v2Sorsa API v3
ユーザー名でユーザー取得GET /2/users/by/username/:usernameGET /info?username=:username
ID でユーザー取得GET /2/users/:idGET /info?user_id=:id
複数ユーザーGET /2/users?ids=...GET /info-batch?usernames=...
フォロワーGET /2/users/:id/followersGET /followers?user_id=:id
フォロー中GET /2/users/:id/followingGET /follows?user_id=:id
認証済みフォロワー利用不可GET /verified-followers?user_id=:id
アカウントの「about」メタデータ利用不可GET /about?username=:username

GET /info-batch は1回の呼び出しで最大100件のユーザー名または ID を受け取ります。GET /followersGET /follows は1ページあたり最大200件の完全なプロフィールを返します。公式のエンドポイントは ID を返し、それを2回目の呼び出しでリハイドレートすることになります。

ツイート:

アクション公式 X API v2Sorsa API v3
単一ツイートGET /2/tweets/:idPOST /tweet-info
複数ツイートGET /2/tweets?ids=...POST /tweet-info-bulk
ユーザータイムラインGET /2/users/:id/tweetsPOST /user-tweets
引用ツイートGET /2/tweets/:id/quote_tweetsPOST /quotes
リツイートした人GET /2/tweets/:id/retweeted_byPOST /retweeters
返信(コメント)専用エンドポイントなしPOST /comments
長文の記事(Article)利用不可POST /article

POST /tweet-info-bulk は1回のリクエストで最大100件のツイートを返します。/tweet-info をループすれば100かかるところです。POST /user-tweets には3,200ツイートの上限がありません。next_cursor でアカウントの最初の投稿までページネーションします。本文フィールドの tweet_link は、完全な URL または数値の ID だけを受け付けます。リクエスト数を削減するパターンについては、API 利用の最適化ガイド をご覧ください。

検索:

アクション公式 X API v2Sorsa API v3
最近または全アーカイブの検索GET /2/tweets/search/recentPOST /search-tweets
メンションGET .../search/recent?query=@userPOST /mentions
ユーザー検索利用不可POST /search-users

POST /search-tweets は同じエンドポイントで過去の検索をカバーし、POST /mentions は公式 API が公開しないエンゲージメントフィルターを加えます。min_likesmin_repliesmin_retweetssince_dateuntil_date です。

リスト、コミュニティ、認証、分析もマッピングされ、いくつかは公式 API に相当するものがありません。リストは GET /list-membersGET /list-followersGET /list-tweets を使います。公式 API がまったく公開しないコミュニティは、POST /community-membersPOST /community-tweetsPOST /community-search-tweets を使います。1回の呼び出しで済む確認系エンドポイント(POST /check-followGET /check-commentPOST /check-quotedPOST /check-retweetPOST /check-community-member)は、そうでなければフォロワーやリツイートした人の完全なリストをクロールする必要がある yes/no の問いに答えます。パスごとの完全な表は、エンドポイントマッピングのリファレンス にあります。


ステップ3:レスポンスのパースをフラット化し、フィールドを再マッピングする {#step-3-flatten-response-parsing-and-remap-fields}

このステップは、認証の次に最も多くのコードに触れます。公式 API は、レスポンスを dataincludesmeta に分けます。フラット料金のプロバイダーは、各ツイートの中に作者を埋め込んだフラットなオブジェクトを返すため、ユーザー結合のロジックが消えます。

ユーザープロフィール、以前:

json
{
  "data": {
    "id": "44196397",
    "name": "Elon Musk",
    "username": "elonmusk",
    "public_metrics": {
      "followers_count": 100000000,
      "following_count": 500,
      "tweet_count": 30000
    }
  }
}

以後:

json
{
  "id": "44196397",
  "username": "elonmusk",
  "display_name": "Elon Musk",
  "followers_count": 100000000,
  "followings_count": 500,
  "tweets_count": 30000,
  "verified": false,
  "created_at": "2009-06-02T20:12:29Z"
}

フィールド名の変更は小さいですが、テストで見落としやすいものです。一度マッピングすれば、あとはそれに従います。

公式 X API v2Sorsa API備考
namedisplay_name改名
textfull_text改名
public_metrics.followers_countfollowers_countフラット化
public_metrics.following_countfollowings_countフラット化、"s" が追加
public_metrics.tweet_counttweets_countフラット化、改名
public_metrics.like_countlikes_countフラット化、"s" が追加
public_metrics.retweet_countretweet_countフラット化、"s" なし
public_metrics.impression_countview_countフラット化、改名
conversation_idconversation_id_str改名
in_reply_to_user_idin_reply_to_usernameID ではなくハンドル
author_idincludes.users[]user(完全なオブジェクトがインライン)埋め込み
includes 経由の参照ツイートquoted_statusretweeted_statusインライン

デバッグの時間を奪わないよう1つの不整合を書き留めておきます。いいねとフォローは複数形になり(likes_countfollowings_count)、一方 retweet_countreply_countquote_count は単数形のままです。作者プロフィールはすべてのツイートレスポンスの user の下にあるため、公式 API で維持していた includes.users の照合テーブルは、まるごと削除できます。


ステップ4:ページネーションをカーソルに切り替える {#step-4-switch-pagination-to-a-cursor}

ページネーションは1つのフィールドに畳み込まれます。公式 API はクエリで pagination_token を使い、meta.next_token を返します。フラット料金のプロバイダーは、レスポンスのトップレベルで next_cursor を使います。

GET エンドポイントでは、next_cursor をクエリパラメータとして渡します。POST エンドポイントでは、JSON 本文に含めます。

bash
curl -X POST "https://api.sorsa.io/v3/search-tweets" \
  -H "ApiKey: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "query": "from:elonmusk", "next_cursor": "ABC123" }'

レスポンスはフラットで、next_cursor が欠けているか null なら、最後のページに到達したことを意味します。

json
{ "tweets": [], "next_cursor": "XYZ789" }

フォロワーリストのページネーションを含む完全なパターンは、カーソルページネーションのドキュメント にあります。


ステップ5:必要なところで GET を POST に切り替える {#step-5-switch-get-to-post-where-required}

これは、チームが忘れて、それから10分デバッグする変更です。公式 API では、ツイートと検索の読み取りは GET です。フラット料金の REST API では、ツイート識別子または検索クエリを受け取るものは POST になり、一方ユーザー識別子またはリスト ID を受け取るものは GET のままです。

アクション公式 APISorsa API
ツイートを取得GETPOST
ツイートを検索GETPOST
ユーザータイムラインGETPOST
引用ツイート、リツイートした人GETPOST
返信(コメント)該当なしPOST
ユーザープロフィールGETGET
フォロワー、フォロー中GETGET
リストGETGET

経験則:ツイート識別子または検索クエリなら POST、ユーザー識別子またはリスト ID なら GET です。


ステップ6:コードを移行する {#step-6-migrate-the-code}

代表的な3つの移行を、それぞれ curl、Python、JavaScript で示します。これらは、読み取り系のコードベース全体で繰り返すパターンです。

ユーザープロフィールを取得する

以前:

python
import requests

r = requests.get(
    "https://api.x.com/2/users/by/username/elonmusk",
    params={"user.fields": "public_metrics,verified,created_at"},
    headers={"Authorization": f"Bearer {BEARER_TOKEN}"},
)
user = r.json()["data"]
followers = user["public_metrics"]["followers_count"]
name = user["name"]

以後:

python
import requests

r = requests.get(
    "https://api.sorsa.io/v3/info",
    params={"username": "elonmusk"},
    headers={"ApiKey": API_KEY},
)
user = r.json()
followers = user["followers_count"]
name = user["display_name"]

フィールド選択の文字列は消え、指標はトップレベルです。

ツイートを検索する

以前は、作者の結合が必須です。

javascript
const params = new URLSearchParams({
  query: "from:elonmusk since:2024-01-01",
  "tweet.fields": "created_at,public_metrics",
  expansions: "author_id",
  "user.fields": "username,name",
});
const res = await fetch(`https://api.x.com/2/tweets/search/recent?${params}`, {
  headers: { Authorization: `Bearer ${BEARER_TOKEN}` },
});
const data = await res.json();
const users = Object.fromEntries((data.includes?.users || []).map(u => [u.id, u]));
for (const t of data.data || []) {
  console.log(t.text, "by", users[t.author_id].username);
}

以後は、各ツイートがすでにその作者を運びます。

javascript
const res = await fetch("https://api.sorsa.io/v3/search-tweets", {
  method: "POST",
  headers: { ApiKey: API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ query: "from:elonmusk since:2024-01-01" }),
});
const data = await res.json();
for (const t of data.tweets) {
  console.log(t.full_text, "by", t.user.username);
}

作者がすべてのツイートに埋め込まれているため、ユーザー結合のテーブルは消えます。

すべてのフォロワーをページネーションする

python
def fetch_all_followers(user_id, api_key):
    url = "https://api.sorsa.io/v3/followers"
    headers = {"ApiKey": api_key}
    followers, next_cursor = [], None
    while True:
        params = {"user_id": user_id}
        if next_cursor:
            params["next_cursor"] = next_cursor
        data = requests.get(url, headers=headers, params=params).json()
        followers.extend(data.get("users", []))
        next_cursor = data.get("next_cursor")
        if not next_cursor:
            break
    return followers

各ページは最大200件の完全にハイドレートされたプロフィールを返すため、公式 API では ID の呼び出しに加えてリハイドレートの呼び出しが必要だったフォロワーグラフの取得が、1回のパスになります。言語別の詳細については、Twitter API Python ガイド が完全な読み取り側の処理を扱います。


ステップ7:検索クエリを保ち、エラーを処理する {#step-7-keep-your-search-queries-and-handle-errors}

検索クエリはそのまま移ります。同じ Twitter の高度な検索コマンドをサポートするフラット料金のプロバイダーは、from:to:since:until:、引用符付きのフレーズ、ハッシュタグ、OR-is:retweet を、公式の最近検索エンドポイントとまったく同じように読むため、既存のクエリ文字列は編集不要です。

既存のクエリ文字列をそのままコピーします。高度な検索コマンドのリファレンス が完全なセットを列挙します。mentions エンドポイントもエンゲージメントフィルター(min_likesmin_repliesmin_retweetssince_dateuntil_date)を公開するため、公式 API に対して書いた「エンゲージメントの下限でフィルター」というクライアント側のロジックは、サーバー側に移せます。

エラー処理も単純化します。公式 API が構造化されたエラー配列を返すところで、フラット料金のプロバイダーは、標準のステータスコードとともに単一の message フィールドを返します。400、401、403、404、429、500 です。429 の場合の方針は、1秒待って再試行することです。制限が、追跡すべきエンドポイント別の窓のない、すべてのエンドポイントとプランで一律の毎秒20リクエストだからです。防御的なリトライのラッパー:

python
import time, requests

def call_with_retry(method, url, max_retries=3, **kwargs):
    for attempt in range(max_retries):
        r = requests.request(method, url, **kwargs)
        if r.status_code == 429:
            time.sleep(2 ** attempt)
            continue
        r.raise_for_status()
        return r.json()
    raise RuntimeError(f"failed after {max_retries} retries")

ステータスコードの完全なリストは、エラーコードのリファレンス にあります。


移行チェックリスト {#migration-checklist}

読み取り側の移行のための作業リストとして、これを使ってください。

  1. Authorization: Bearer ... を、いたるところで ApiKey ヘッダーに置き換える。
  2. OAuth 1.0a の署名ロジック(コンシューマーキー、アクセストークン、署名)を取り除く。
  3. ベース URL を https://api.sorsa.io/v3 に更新する。
  4. ステップ2の表を使って、すべてのエンドポイントのパスを再マッピングする。
  5. ツイート、検索、コメント、引用、リツイートした人のエンドポイントについて、GET を POST に切り替える。
  6. tweet.fieldsuser.fieldsexpansions を消す。
  7. data / includes / meta の展開を取り除く。
  8. モデルのフィールド名を変える(nametextpublic_metrics ブロック)。
  9. pagination_tokennext_tokennext_cursor に置き換える。
  10. 単一の message フィールドの形に合わせて、エラー処理を更新する。
  11. レート制限のロジックを、エンドポイント別の窓のない、一律の毎秒20リクエストに設定する。
  12. デプロイ前に、API Playground で重要なエンドポイントをテストする。
  13. key-usage-info エンドポイントでクォータの消費を追跡する(キー使用状況のリファレンス を参照)。
  14. X にも書き込むなら、公式のキーを保つ。移るのは読み取り側だけです。

実例:ある学術グループのアーカイブ移行 {#in-practice-an-academic-groups-archival-migration}

ある学術研究グループが、2025年半ばに、縦断的研究のためのツイート収集パイプラインを公式 API で運用した状態で、相談に来ました。2つの問題が同グループを行き詰まらせていました。ユーザータイムラインのエンドポイントがアカウントあたり最新の3,200ツイートで上限に達し、それが過去のカバレッジを壊していたこと、そしてフォロワーのエンドポイントが、使えるプロフィールになるには2回目の照会が必要な、むき出しの ID を返していたことです。

読み取り側の移行は、1人の研究者で約2日かかりました。POST /user-tweets が3,200ツイートの上限を取り除き、next_cursor で各アカウントの最初の投稿へと向かってページネーションしたため、アーカイブの空白が埋まりました。GET /followers が1ページあたり最大200件の完全なプロフィールを返すため、フォロワーの取得は1回のパスに畳み込まれ、それが仕事のその部分のリクエスト数をおよそ半分にしました。唯一の本当の摩擦はフィールド名の変更(namedisplay_nametextfull_text、そして複数形の likes_count)で、設計ではなく数時間のテストで捕らえました。正確な数字はワークロードによって変わりますが、移行の形は変わりません。


よくある質問 {#frequently-asked-questions}

Twitter/X API から1エンドポイントずつ移行できる?

はい、そして段階的な移行はたいていより安全な経路です。データ呼び出しの前に薄い抽象化層を置き、1つのエンドポイントを新しいプロバイダーに向け、その出力を数日間公式 API に対して検証し、それから次を移します。アプリケーションのコードが1回の大きな書き直しで変わる必要は決してなく、何かがおかしく見えれば、1つのエンドポイントを独立してロールバックできます。

本番を壊さずに Twitter/X API の移行をどうテストする?

切り替える前に、2つの API を並行して実行し、パースした出力を差分します。最も軽い選択肢は、統合コードなしでリクエストを送るブラウザの playground です。より強い選択肢は、両方の API を呼んで結果を比較する並列のスクリプトで、最も徹底的なのは、トラフィックの一定割合を新しいプロバイダーにルーティングして、即座にロールバックできるフィーチャーフラグです。

移行後も Twitter の高度な検索クエリは動く?

はい、プロバイダーが同じ高度な検索コマンドをサポートすれば。Sorsa の search-tweets と mentions のエンドポイントは、from:、to:、since:、until:、引用符付きのフレーズ、ハッシュタグ、OR、-is:retweet を、公式の最近検索エンドポイントとまったく同じように読むため、既存のクエリ文字列は編集なしで移ります。mentions エンドポイントは、公式 API が公開しない min_likes や min_retweets のようなエンゲージメントフィルターを加えます。

移行後、レート制限とエラーをどう処理する?

エラー処理は単純になります。Sorsa は各エラーを、公式 API の構造化されたエラー配列ではなく、標準のステータスコード(400、401、403、404、429、500)とともに単一の message フィールドとして返します。レート制限は、エンドポイント別の15分の窓のない、すべてのプランで一律の毎秒20リクエストです。429 の場合は、1秒待って再試行します。追跡すべきリセットヘッダーはありません。

移行すると3,200ツイートのタイムライン制限はなくなる?

はい。公式 v2 のユーザータイムラインエンドポイントは、アカウントあたり最新の3,200ツイートで上限に達します。Sorsa の POST /user-tweets にはそのような上限がありません。レスポンスが next_cursor を返さなくなるまで next_cursor でページネーションすれば、アカウントの最初の投稿に到達します。アーカイブ、感情の履歴、学習データの作業には、これがそもそも移行が起きる理由であることがしばしばです。

代替が読み取り専用なら、X への書き込みをどう続ける?

書き込みアクションには公式 X API を保ち、読み取り経路だけを移行します。投稿、返信、DM、いいね、フォロー、広告はすべて公式 API に残り、それがユーザーを代行して行動できる唯一のシステムです。ほとんどのチームは混合に落ち着きます。書き込み用の小さな公式 API の予算と、大量のデータ取得用のフラット料金の読み取り API です。Sorsa は設計上読み取り専用で、それは書き込み権限とアカウント凍結のリスクの一群も取り除きます。


本ガイドの検証方法 {#how-we-verified-this-guide}

この解説は、2022年以降、代替の X API を運用する当チームの実地の作業と、公式のプラットフォームを離れるチームのために実行してきた読み取り側の移行に基づいています。エンドポイントのパス、パラメータ名、レスポンスフィールド、そして ApiKey ヘッダーは、稼働中の Sorsa API ドキュメント に対して確認し、公式 X API の認証モデル、レスポンスの包み、従量課金レートは、2026年4月20日の変更を反映して、X の開発者ドキュメントと料金ページに対して確認しました。決定のコスト面については、現行の X API 料金の詳細 をご覧ください。すべてのコードサンプルは、示したとおりに動くように書かれています。2026年6月13日確認。

監修:Keksich(Sorsa創業者、マーケター兼X APIリサーチャー)


始め方 {#getting-started}

パイプラインがツイート、プロフィール、フォロワー、または検索結果を読むなら、移行のスコープを見積もる最も速い方法は、いくつかの呼び出しを実行し、レスポンスの形を現在のパーサーと比較することです。Sorsa API のクイックスタート が、1つのキーの背後で数分で最初のリクエストまで導き、過去データのドキュメント が、タイムラインのエンドポイントが3,200ツイートの上限なしで2006年まで遡る方法を示します。最初の100回分のリクエストは、1回限り・クレジットカード不要・有効期限なしで無料で、支払う前に完全な並行差分のテストをカバーします。その後は、読み取りは1,000ツイートあたり$0.02、1,000プロフィールあたり$0.01から動作し、プランは10,000リクエストで月額$49から、すべての層で同じ一律の毎秒20リクエスト、そして最初の呼び出しまでに開発者アカウントの審査はありません。1つのエンドポイントをフラグの背後でそれに向け、出力を差分し、残りを移行しましょう。