本文へスキップ

Twitter/X API から移行する方法

公式 X API からの移行で必要なコード変更は5つです。v2 の各エンドポイントを新しいパスに対応させ、OAuth を API キーに置き換え、フィールドと expansions を外し、JSON のパースを書き直し、ページネーショントークンを切り替えます。

結論: 移行すべきなのは読み取りです。公式 X API は1投稿あたり$0.005、1ユーザーあたり$0.010を課金し、2026年11月22日からは投稿に付いて返る投稿者1人ごとに$0.010が加算されます。読み取りの移行先には Sorsa API を使います。ApiKey ヘッダー1つで、検索は1リクエスト20ツイート、1,000件あたり$0.07からです。対応表の検証には、100回分の無料リクエストを使えます。

エンドポイント
POST /search-tweets
1ページの件数
1リクエストあたり20ツイート
料金
1,000件あたり$0.07から

#2023年以降、公式 X API で何が変わったか

公式 X API 向けに書かれたコードは、ほとんどそのまま動きます。2023年以降に変わったのは、呼び出せるもの、返ってくるもの、そしてレスポンスごとの費用です。以下の日付入りの一覧は、X の変更履歴(新しいタブで開きます)と開発者フォーラムのお知らせに基づき、各変更が連携のどこに影響するかを添えたものです。

日付公式 X API の変更コードへの影響
2023年3月30日無料アクセスが有料プランに置き換わり、Free は書き込み専用に無料アプリでの読み取りがエラーになる
2023年6月26日Basic と Pro からフォロー系エンドポイントが削除そのプランでフォロワーとフォロー中の呼び出しが止まる
2023年9月28日v1.1 の search/tweets と users/search が非推奨にv1.1 の検索コードは新しいエンドポイントが必要
2026年1月19日検索から min_likes、min_replies、min_reposts が削除これらを使ったクエリは5月4日まで外す必要があった
2026年2月6日従量課金がモデルになり、クレジットは前払い残高がゼロになると呼び出しがブロックされる
2026年2月23日プログラムによる返信は、投稿者から呼びかけられた場合のみ許可返信ボットが拒否される
2026年4月20日セルフサービスからフォロー、いいね、引用ポストが削除これらの書き込み呼び出しが動かなくなる
2026年5月4日検索が新しいインデックスに移行し、キーワード検索結果からリツイートが除外同じクエリで返る投稿が減る
2026年6月1日と9月1日旧 Basic、続いて旧 Pro が従量課金へ移行月間の上限が、リソースごとの請求に変わる
2026年11月22日expansions が課金対象に:投稿者1人$0.010、メディア1件$0.005expansions=author_id で投稿1件の費用が3倍になり得る
  • 2023年3月30日

    公式 X API の変更
    無料アクセスが有料プランに置き換わり、Free は書き込み専用に
    コードへの影響
    無料アプリでの読み取りがエラーになる
  • 2023年6月26日

    公式 X API の変更
    Basic と Pro からフォロー系エンドポイントが削除
    コードへの影響
    そのプランでフォロワーとフォロー中の呼び出しが止まる
  • 2023年9月28日

    公式 X API の変更
    v1.1 の search/tweets と users/search が非推奨に
    コードへの影響
    v1.1 の検索コードは新しいエンドポイントが必要
  • 2026年1月19日

    公式 X API の変更
    検索から min_likes、min_replies、min_reposts が削除
    コードへの影響
    これらを使ったクエリは5月4日まで外す必要があった
  • 2026年2月6日

    公式 X API の変更
    従量課金がモデルになり、クレジットは前払い
    コードへの影響
    残高がゼロになると呼び出しがブロックされる
  • 2026年2月23日

    公式 X API の変更
    プログラムによる返信は、投稿者から呼びかけられた場合のみ許可
    コードへの影響
    返信ボットが拒否される
  • 2026年4月20日

    公式 X API の変更
    セルフサービスからフォロー、いいね、引用ポストが削除
    コードへの影響
    これらの書き込み呼び出しが動かなくなる
  • 2026年5月4日

    公式 X API の変更
    検索が新しいインデックスに移行し、キーワード検索結果からリツイートが除外
    コードへの影響
    同じクエリで返る投稿が減る
  • 2026年6月1日と9月1日

    公式 X API の変更
    旧 Basic、続いて旧 Pro が従量課金へ移行
    コードへの影響
    月間の上限が、リソースごとの請求に変わる
  • 2026年11月22日

    公式 X API の変更
    expansions が課金対象に:投稿者1人$0.010、メディア1件$0.005
    コードへの影響
    expansions=author_id で投稿1件の費用が3倍になり得る

このうち2つは、コードを1行も変えていないのに結果が変わりました。5月4日に新しい検索インデックス(新しいタブで開きます)が稼働して以降、4月には問題なく動いたキーワード検索が、リツイートなしで返ってきます。そして11月22日からは、1,000投稿あたり$5.00だった expansions=author_id 付きの同じリクエストが最大$15.00になります。X のexpansions のお知らせ(新しいタブで開きます)が、返される投稿者をユーザーの読み取りとして課金するためです。

稼働中のコードの足元でプランも変わりました。X は旧 Basic(新しいタブで開きます) のサブスクリプションを2026年6月1日以降の切り替え日に終了し、旧 Pro(新しいタブで開きます) は9月1日以降に終了しました。月払いの Pro アカウントには、切り替え時に$5,000のクレジットが請求され、自動チャージがオンになりました。現在、読み取りはすべて、1投稿あたり$0.005、1ユーザーまたはフォロワーあたり$0.010のクレジットから差し引かれ、月300万投稿の読み取りが上限です。全料金表はX API の料金の解説にまとめています。

#Sorsa API に移るものと X に残るもの

公開データの読み取りはすべて移行できます。検索、タイムライン、投稿とユーザーの取得、フォロワー、リスト、引用、リツイートしたユーザー、リプライ、スペース、コミュニティ、トレンドです。2022年から本番稼働し、累計60億リクエスト以上を処理してきた Twitter(X)API 兼スクレイピングツールである Sorsa API は、これらをすべてキー1つで返します。OAuth は不要で、開発者アカウントなしで使う Twitter APIのガイドで説明しているとおり、X の開発者アカウントも要りません。設計上、読み取り専用なので、書き込みの呼び出しは今の場所に残ります。

次のものは、公式 X API に残します。

  • 投稿、返信、削除、メディアのアップロード。
  • ダイレクトメッセージ、およびいいね、フォロー、ブックマーク、ブロック(操作としても、自分のアカウントの活動一覧としても)。
  • 投稿にいいねしたユーザー、および非公開アカウントのあらゆるデータ。
  • フィルタードストリーム、ファイアホース、Webhook、X Activity API:Sorsa API はプッシュではなくポーリングで使います(ポーリングのパターンは下で紹介します)。
  • 投稿数のカウント、コミュニティノート、Ads API。

読み取りに X アカウントは関与しません。Sorsa API が、ログイン情報や Cookie を求めることはないからです。移行後に公式 X API が課金するのは、書き込みだけです。リクエストごとに、投稿の作成が$0.015、リンク付きの投稿が$0.20です。

お金がかかるのは読み取りです。検索とタイムラインの読み取りで、100万ツイートは公式 X API なら投稿者への課金を除いても$5,000かかり、Sorsa API なら$70からです。Sorsa API は各ツイートの投稿者を追加料金なしで一緒に返します。ID での取得はさらに安く、POST /tweet-info-bulk は1リクエストで100ツイートを返し、1,000件あたり$0.014からです。

検索で読み取るツイート数

公式 X API

$5,000

1Mツイートの場合

1投稿あたり$0.005、投稿者を除く

Sorsa API

$70から

1Mツイートの場合

1,000件あたり$0.07から、投稿者込み

#6ステップで Sorsa API へ移行する

v2 の連携を棚卸しから切り替えまで進めるのに、6つのステップがあります。最初の5つはコードの修正で、最後の1つは両方の API が同じ読み取りに答える状態で数日間続けます。

  1. コードが行うすべての呼び出しを洗い出す。 コード内で api.x.com/2、api.twitter.com/2、使っているクライアントのメソッド名(Tweepy の search_recent_tweets や xdk の posts.search_recent など)を検索します。X の Developer Console から先月の利用状況をリソース別にエクスポートし、各呼び出しを読み取りか書き込みかに分類してください。

  2. キーを取得して1リクエスト送る。 サインアップして、ダッシュボードからキーをコピーし、いちばん多く使っている読み取りに代わるエンドポイントを呼び出します。

    ターミナル
    curl -X POST "https://api.sorsa.io/v3/search-tweets" \
      -H "ApiKey: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"query": "from:mira_moves_reads", "order": "latest"}'
    
    レスポンス200 OK
    {
      "tweets": [
        {
          "id": "1852331946812457001",
          "full_text": "Read path moved. Writes stay where they were.",
          "created_at": "2026-10-30T14:05:12Z",
          "lang": "en",
          "likes_count": 41,
          "retweet_count": 6,
          "reply_count": 3,
          "quote_count": 1,
          "view_count": 2380,
          "conversation_id_str": "1852331946812457001",
          "is_reply": false,
          "user": {
            "id": "1592041833471827968",
            "username": "mira_moves_reads",
            "display_name": "Mira Moves Reads",
            "followers_count": 912,
            "verified": false
          }
        }
      ],
      "next_cursor": "DAADDAABCgABGbHk7m4WYAEKAAIZsd"
    }
    
  3. 各読み取りをエンドポイントに対応させる。 次のセクションの表と変換ツールが、v2 の読み取りごとに Sorsa API のパスとパラメータを対応させています。

  4. OAuth、フィールド、expansions を1つのヘッダーに置き換える。 読み取りの呼び出しでは、Bearer トークンや OAuth 1.0a の署名を削除し、代わりに ApiKey: YOUR_API_KEY を送ります。tweet.fields、user.fields、expansions は外してください。すべてのフィールドと投稿者が、デフォルトで返ってきます。

  5. パースとページネーションを書き直す。 data と includes を読んでいた箇所で tweets または users を読み、名前が違うフィールドを置き換え、next_cursor が空で返るまで送り返します。

  6. シャドウランを実施してから切り替える。 読み取りのコピーを数日間 Sorsa API に送って結果を比較し、トラフィックを段階的に移し、X のキーは書き込み用に残します。

#X API v2 の各エンドポイントを Sorsa API に対応させる

連携が呼び出しそうな公開データの読み取りには、それぞれ対応する Sorsa API のエンドポイントがあります。下の表は、X の OpenAPI 仕様(新しいタブで開きます)に書かれた v2 のパスごとに、置き換え先と、1リクエストで返る量を対応させたものです。Sorsa API は件数ではなくリクエスト単位で課金するため、最後の列が料金を決めます。

X API v2 の呼び出しSorsa API のエンドポイント1リクエストで返る量
GET /2/tweets/search/recentPOST /search-tweets(新しいタブで開きます)約20ツイート
GET /2/tweets/search/allsince: と until: を付けた POST /search-tweets約20ツイート
GET /2/tweets/:idPOST /tweet-info(新しいタブで開きます)1ツイート
GET /2/tweets(ID 指定)POST /tweet-info-bulk(新しいタブで開きます)最大100ツイート
GET /2/users/:id/tweetsPOST /user-tweets(新しいタブで開きます)約20ツイート
GET /2/users/:id/mentionsPOST /mentions(新しいタブで開きます)約20ツイート
GET /2/tweets/:id/quote_tweetsPOST /quotes(新しいタブで開きます)約20ツイート
GET /2/tweets/:id/retweeted_byPOST /retweeters(新しいタブで開きます)プロフィール1ページ分
conversation_id: を使った検索POST /comments(新しいタブで開きます)約20件のリプライ
GET /2/users/by/username/:usernameGET /info(新しいタブで開きます)1プロフィール
GET /2/users と GET /2/users/byGET /info-batch(新しいタブで開きます)最大100プロフィール
GET /2/users/:id/followersGET /followers(新しいタブで開きます)最大200プロフィール
GET /2/users/:id/followingGET /follows(新しいタブで開きます)最大200プロフィール
GET /2/users/searchPOST /search-users(新しいタブで開きます)プロフィール1ページ分
GET /2/lists/:id/membersGET /list-members(新しいタブで開きます)プロフィール1ページ分
GET /2/lists/:id/followersGET /list-followers(新しいタブで開きます)プロフィール1ページ分
GET /2/lists/:id/tweetsGET /list-tweets(新しいタブで開きます)約20ツイート
GET /2/spaces/:idGET /spaces(新しいタブで開きます)1スペース
GET /2/communities/:idPOST /community-info(新しいタブで開きます)1コミュニティ
GET /2/communities/searchPOST /search-communities(新しいタブで開きます)コミュニティ1ページ分
GET /2/trends/by/woeid/:woeidGET /trends(新しいタブで開きます)1地域のトレンド
GET /2/usage/tweetsGET /key-usage-info(新しいタブで開きます)残高(無料)

パスの変更に伴い、4つの習慣が変わります。ツイート系のエンドポイントは JSON ボディを持つ POST で、ユーザー、リスト、ユーティリティの取得は GET のままです。v2 が数値 ID を求めていた場所でも、ユーザー名が使えるため、タイムライン呼び出しの前に必要だった追加の取得は不要になります。tweet_link には、投稿の完全な URL でも ID だけでも渡せます。そして POST /user-tweets は、X のユーザータイムラインが止まる最新3,200件を超えてもページをたどれます。

v2 の読み取りの一部には対応するものがありません。Sorsa API が返すのは、公開プロフィールで誰でも見られる情報だけだからです。投稿にいいねしたユーザー、ユーザーのいいねとブックマーク、投稿数のカウント、ホームタイムライン、すべてのストリームがそれにあたります。逆に、v2 では答えられないことにも答えます。POST /check-follow(新しいタブで開きます) は、あるアカウントが別のアカウントをフォローしているかを1リクエストで返し、GET /about(新しいタブで開きます) はアカウントの国とユーザー名の履歴を返します。

下の変換ツールは、v2 の呼び出しを1つずつ Sorsa API のリクエストに変換し、変わるフィールドと、双方の1,000件あたりの料金を示します。

変更前: 公式X API v2
curl -G "https://api.x.com/2/tweets/search/recent" \
  -H "Authorization: Bearer $X_BEARER_TOKEN" \
  --data-urlencode "query=from:mira_moves_reads" \
  -d "max_results=100" \
  -d "expansions=author_id" \
  -d "tweet.fields=created_at,public_metrics"
変更後: Sorsa API
curl -X POST "https://api.sorsa.io/v3/search-tweets" \
  -H "ApiKey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "from:mira_moves_reads", "order": "latest"}'
Sorsa APIのエンドポイント
POST /search-tweets
1回のリクエストで返る数
約20ツイート
公式X API、1,000件の結果
$5.00、さらに投稿者分で最大$10.00
Sorsa API、1,000件の結果
最安 $0.07

ページネーション: next_token は次に置き換わります: next_cursor (JSONボディで送ります)。同じクエリで、7日ではなく2006年までさかのぼって検索でき、すべてのツイートに投稿者情報が付きます。

コードで使っているv2の呼び出しを選ぶと、Sorsa APIでの同じリクエストと、それぞれでの1,000件の費用が表示されます。

フィルタードストリームや Webhook を使っているコードの場合

Sorsa API にはストリームも Webhook もないため、フィルタードストリームのルールは、ポーリングする検索クエリに置き換わります。ルールを POST /search-tweets のクエリとして "order": "latest" 付きで送り、保存済みの最新 ID を since_id: に指定し、next_cursor をたどります。こうすれば、新規投稿が20件を超える忙しい1分間でも、取りこぼしはありません。

poll.py
import os
import time
import requests

URL = "https://api.sorsa.io/v3/search-tweets"
HEADERS = {"ApiKey": os.environ.get("SORSA_API_KEY", "YOUR_API_KEY")}
RULE = '"open data" lang:en'


def new_tweets(since_id):
    body = {"query": f"{RULE} since_id:{since_id}", "order": "latest"}
    found = []
    while True:
        r = requests.post(URL, headers=HEADERS, json=body, timeout=30)
        if r.status_code == 429:
            time.sleep(1)
            continue
        r.raise_for_status()
        page = r.json()
        found += page.get("tweets", [])
        if not page.get("next_cursor"):
            return found
        body["next_cursor"] = page["next_cursor"]


newest = "1852331946812457001"
while True:
    tweets = new_tweets(newest)
    for t in sorted(tweets, key=lambda t: int(t["id"])):
        print(t["id"], t["user"]["username"], t["full_text"][:60])
    if tweets:
        newest = max((t["id"] for t in tweets), key=int)
    time.sleep(15)

1つのキーは、どのプランでも毎秒20リクエストで動作します。これは、ルール全体で共有する1ページ分のポーリングなら1分間に1,200回になります。監視ジョブのスケジュール、アラート、重複排除については、リアルタイム Twitter 監視のガイドで扱っています。

#レスポンスのパースを書き直す

Sorsa API は、各ツイートを投稿者込みの1つのオブジェクトとして返します。そのため data と includes の結合は不要になり、残る作業はフィールド名の置き換えです。

公式X API v2 Sorsa API GET /2/tweets/search/recent { "data": [{ "id": "18523319…", "text": "Read path moved…", "author_id": "15920418…", "public_metrics": { "like_count": 41 } }], "includes": {"users": [{ "id": "15920418…", "username": "mira_moves_reads" }]}, "meta": {"next_token": "b26v8…"} } 1 2 3 4 3 5 POST /search-tweets { "tweets": [{ "id": "18523319…", "full_text": "Read path moved…", "user": { "id": "15920418…", "username": "mira_moves_reads" }, "likes_count": 41 }], "next_cursor": "DAADDAAB…" } 1 2 3 4 5 投稿者はすべてのツイートに内包: フィールド指定もexpansionsも不要。
X API v2 はツイートと投稿者を分けて返し、next_token でページを送る。Sorsa API は投稿者をツイートの中に入れ、next_cursor でページを送る。

以下のフィールド名は、2026年11月2日に確認した X のデータディクショナリと Sorsa API の公開リファレンスに基づいています。多くの変更はフラット化で、メトリクスが public_metrics から出て同じ名前のまま残ります。それ以外は一部の名前の変更です。

X API v2 のフィールドSorsa API のフィールド
data[]tweets[]。アカウントの一覧は users[]
text と note_tweet.textfull_text
author_id と includes.users[]user(プロフィール全体)
public_metrics.like_countlikes_count
public_metrics.impression_countview_count
public_metrics.retweet_count、reply_count、quote_count、bookmark_count同じ名前で、1階層上
conversation_idconversation_id_str
referenced_tweets と includes.tweets[]quoted_status、retweeted_status、in_reply_to_tweet_id
attachments.media_keys と includes.media[]type、link、preview を持つ entities[]
ユーザーの namedisplay_name
ユーザーの public_metrics.following_count、tweet_countfollowings_count、tweets_count
meta.next_token と pagination_tokennext_cursor(どちらの方向も)
title と detail を持つ errors[]{"message": "..."}
  • data[]

    Sorsa API のフィールド
    tweets[]。アカウントの一覧は users[]
  • text と note_tweet.text

    Sorsa API のフィールド
    full_text
  • author_id と includes.users[]

    Sorsa API のフィールド
    user(プロフィール全体)
  • public_metrics.like_count

    Sorsa API のフィールド
    likes_count
  • public_metrics.impression_count

    Sorsa API のフィールド
    view_count
  • public_metrics.retweet_count、reply_count、quote_count、bookmark_count

    Sorsa API のフィールド
    同じ名前で、1階層上
  • conversation_id

    Sorsa API のフィールド
    conversation_id_str
  • referenced_tweets と includes.tweets[]

    Sorsa API のフィールド
    quoted_status、retweeted_status、in_reply_to_tweet_id
  • attachments.media_keys と includes.media[]

    Sorsa API のフィールド
    type、link、preview を持つ entities[]
  • ユーザーの name

    Sorsa API のフィールド
    display_name
  • ユーザーの public_metrics.following_count、tweet_count

    Sorsa API のフィールド
    followings_count、tweets_count
  • meta.next_token と pagination_token

    Sorsa API のフィールド
    next_cursor(どちらの方向も)
  • title と detail を持つ errors[]

    Sorsa API のフィールド
    {"message": "..."}

ID は文字列のまま、日付は ISO 8601 のままなので、保存済みのデータの形は変わりません。ページネーションは1つの名前にまとまります。GET のエンドポイントではクエリパラメータとして、POST のエンドポイントでは JSON ボディの中に next_cursor を入れて送り、空で返ってきたら止めます。レート制限もシンプルになります。X API のレート制限のガイドで説明している15分ウィンドウと x-rate-limit-* ヘッダーは、キーごとに毎秒20リクエストという1つの制限に置き換わります。429 なら1秒待って再試行し、403 ならキーがないか、その月のリクエストを使い切っています。

知っておきたいこと

ページサイズは固定ではありません。検索の1ページには最大20ツイートが入りますが、後続のページが残っていても、それより少ないことがあります。ループは項目数ではなく必ず next_cursor で回してください。そうしないと、短いページ1つで処理が早く終わってしまいます。

#Tweepy と xdk のコード:移行前後

Tweepy と xdk は無料のライブラリで、請求を生むのはその背後にある公式 X API です。したがって移行で入れ替えるのは言語ではなく、クライアントです。同じ投稿者付きの検索を、最後のページまでたどる処理について、Tweepy と、Sorsa API 上の素の Python で見てみましょう。

import tweepy

client = tweepy.Client(bearer_token="X_BEARER_TOKEN")

for page in tweepy.Paginator(
        client.search_recent_tweets,
        query="from:mira_moves_reads",
        tweet_fields=["created_at", "public_metrics"],
        expansions=["author_id"],
        user_fields=["username", "name"],
        max_results=100):
    users = {u.id: u for u in page.includes.get("users", [])}
    for t in page.data or []:
        author = users[t.author_id]
        print(t.id, author.username, t.public_metrics["like_count"])

フィールドのリスト、includes の結合、ページネーターはコードから消え、ヘッダーが1つ入ります。xdk のコードも同じように変わります。Client(bearer_token=...) と client.posts.search_recent(...) は同じ data と includes をたどるので、上のループに置き換わります。移行後はクエリが届く範囲も広がります。POST /search-tweets はどのプランでも2006年まで遡って検索しますが、v2 の最近の投稿の検索は直近7日間だけです。

Node.js の twitter-api-v2 パッケージで作ったコードも同じ方法で、fetch とヘッダー1つに移れます。

Node.js
const URL = "https://api.sorsa.io/v3/search-tweets";
const headers = {
  ApiKey: process.env.SORSA_API_KEY ?? "YOUR_API_KEY",
  "Content-Type": "application/json",
};

const body = { query: "from:mira_moves_reads", order: "latest" };
do {
  const res = await fetch(URL, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const page = await res.json();
  for (const t of page.tweets ?? []) {
    console.log(t.id, t.user.username, t.likes_count);
  }
  body.next_cursor = page.next_cursor;
} while (body.next_cursor);

読み取りだけの Tweepy コードなら、読み取りを Sorsa API に移します。検索は1リクエスト20ツイートで1,000件あたり$0.07から、プロフィールは1リクエスト100件で1,000件あたり$0.014から、OAuth は不要です。リトライ、バッチ処理、毎秒20リクエスト用のペース制御については、Python で使う Twitter APIのガイドをご覧ください。

#検索クエリを変換する

POST /search-tweets は、x.com の検索ボックスの言語である X の高度な検索の構文を受け付けます。そのため、キーワード、引用符で囲んだフレーズ、from:、to:、lang:、conversation_id:、ハッシュタグはそのまま使えます。変わるのは、v2 の is: と has: の演算子と時間のパラメータで、これらはクエリの中に移ります。

X API v2Sorsa API のクエリ
start_time と end_time パラメータsince:2026-10-01 と until:2026-11-01
since_id と until_id パラメータsince_id:ID と max_id:ID(ID を含む)
sort_order=recency または relevancyボディの "order": "latest" または "popular"
-is:retweet不要:リツイートはデフォルトで含まれない
is:reply と -is:replyfilter:replies と -filter:replies
is:quotefilter:quote
has:media、has:images、has:linksfilter:media、filter:images、filter:links
has:video_linkfilter:native_video
min_likes:、min_reposts:、min_replies:min_faves:、min_retweets:、min_replies:
point_radius:geocode:
  • start_time と end_time パラメータ

    Sorsa API のクエリ
    since:2026-10-01 と until:2026-11-01
  • since_id と until_id パラメータ

    Sorsa API のクエリ
    since_id:ID と max_id:ID(ID を含む)
  • sort_order=recency または relevancy

    Sorsa API のクエリ
    ボディの "order": "latest" または "popular"
  • -is:retweet

    Sorsa API のクエリ
    不要:リツイートはデフォルトで含まれない
  • is:reply と -is:reply

    Sorsa API のクエリ
    filter:replies と -filter:replies
  • is:quote

    Sorsa API のクエリ
    filter:quote
  • has:media、has:images、has:links

    Sorsa API のクエリ
    filter:media、filter:images、filter:links
  • has:video_link

    Sorsa API のクエリ
    filter:native_video
  • min_likes:、min_reposts:、min_replies:

    Sorsa API のクエリ
    min_faves:、min_retweets:、min_replies:
  • point_radius:

    Sorsa API のクエリ
    geocode:

変換したクエリでつまずきやすい点が3つあります。ネイティブのリツイートは、include:nativeretweets を付けない限り除外されます。geocode: は geocode:37.77,-122.41,5km のように緯度が先で、v2 の point_radius: は経度が先でした。そして max_id: は ID 自体を含みますが、v2 の until_id は含みません。演算子の完全な一覧とレシピは、Twitter 検索演算子のガイドにあります。

#シャドウランでテストしてから切り替える

シャドウランとは、公式 X API がアプリへの応答を続けている間に、各読み取りのコピーを Sorsa API に送る方法です。何かが依存する前に、結果を比較できます。

ステージ1 シャドウ X APIが読み取りを すべて担当。Sorsa APIは コピーを受け取る 読み取り X API 100% Sorsa APIはコピー ステージ2 比較 投稿ID、フィールド、 エラーを クエリごとに照合 読み取り X API 100% Sorsa APIはコピー ステージ3 カナリア 読み取りの10%を Sorsa APIが担当。 エラーを監視 読み取り X API 90% Sorsa API 10% ステージ4 切り替え 読み取りはすべて Sorsa APIで実行。 旧読み取りコード削除 読み取り Sorsa API 100% 書き込み 全ステージで公式X API: 投稿、返信、DM
読み取りは4段階で移行し、書き込みは公式 X API から離れない。

このスクリプトは、両方の API で同じ終了済みの1日分に1つのクエリを実行し、投稿 ID を比較します。依存しているクエリとタイムラインごとに実行してください。期間を終了済みにしておくと、新しい投稿で件数がずれることを防げます。

shadow_run.py
import os
import requests

X_URL = "https://api.x.com/2/tweets/search/recent"
X_HEADERS = {"Authorization": "Bearer " + os.environ["X_BEARER_TOKEN"]}
S_URL = "https://api.sorsa.io/v3/search-tweets"
S_HEADERS = {"ApiKey": os.environ.get("SORSA_API_KEY", "YOUR_API_KEY")}


def x_ids(query, start, end):
    params = {"query": query, "start_time": start,
              "end_time": end, "max_results": 100}
    ids = set()
    while True:
        r = requests.get(X_URL, headers=X_HEADERS, params=params, timeout=30)
        r.raise_for_status()
        page = r.json()
        ids |= {t["id"] for t in page.get("data", [])}
        token = page.get("meta", {}).get("next_token")
        if not token:
            return ids
        params["next_token"] = token


def sorsa_ids(query, since, until):
    body = {"query": f"{query} since:{since} until:{until}",
            "order": "latest"}
    ids = set()
    while True:
        r = requests.post(S_URL, headers=S_HEADERS, json=body, timeout=30)
        r.raise_for_status()
        page = r.json()
        ids |= {t["id"] for t in page.get("tweets", [])}
        if not page.get("next_cursor"):
            return ids
        body["next_cursor"] = page["next_cursor"]


query = "from:mira_moves_reads"
a = x_ids(query, "2026-10-30T00:00:00Z", "2026-10-31T00:00:00Z")
b = sorsa_ids(query, "2026-10-30_00:00:00_UTC", "2026-10-31_00:00:00_UTC")
print(f"official X API {len(a)}, Sorsa API {len(b)}, shared {len(a & b)}")
print("only on X:", sorted(a - b)[:10])
print("only on Sorsa API:", sorted(b - a)[:10])

差がゼロになるとは限らず、小さな差が出るのが普通です。2回の呼び出しの間に削除された投稿は消えますし、X のキーワード検索は2026年5月4日からリツイートを除外しています。X の最近の投稿の検索は7日前までなので、期間は直近1週間以内にしてください。最もよく実行する読み取りで ID が揃ったら、段階的に切り替えます。

  • 2つのクライアントを1つのインターフェースと1つの設定フラグの裏に置く。 どの種類の読み取りも、値を1つ変えるだけで元に戻せます。
  • 読み取りの10%を移し、 エラーと GET /key-usage-info(無料で、リクエストにカウントされません)を監視してから、残りを移します。
  • 公式 X API のキーは書き込み専用にして、 Developer Console で利用上限と自動チャージの金額を下げます。
  • 読み取りのコードパス、 フィールドのリスト、古いページネーションは、請求サイクルが1周してから削除します。

#公式 X API から読み取りを移す

読み取り専用の v2 連携は、X の公開データを読むために作られた Twitter API である Sorsa API に、ヘッダー1つ、呼び出しごとに1エンドポイント、結合が不要になったパーサーで移行できます。検索は1,000ツイートあたり$5.00ではなく$0.07から、フォロワーは1,000件あたり$10.00ではなく$0.007からで、どのプランも毎秒20リクエストで動作します。まず最も費用のかかる読み取りから始めて数日間シャドウランを行い、公式 X API は書き込みにだけ残してください。

#FAQ

公式 X API から、どの Twitter API に移行すべきですか

X の公開データの読み取りなら、Sorsa API に移行してください。v2 の各読み取りが1つのエンドポイントに対応し、ツイートは ID 指定で1,000件あたり$0.014から、検索で$0.07からです。公式 X API は$5.00です。どのプランも毎秒20リクエストで動作し、検索は2006年まで遡れます。OAuth の代わりに API キー1つで済みます。Sorsa API は読み取り専用なので、投稿には公式 X API を残してください。

公式 X API からの移行にはどのくらいかかりますか

コードの変更量は、データ量ではなく、v2 の呼び出しの種類の数に比例します。各呼び出しが、1つのエンドポイント、1つのヘッダー、いくつかのフィールド名の変更になります。そこに、両方の API が同じ読み取りに答え、ID を比較する数日間のシャドウランが加わります。投稿、DM、その他の書き込みは公式 X API に残るため、移るのは読み取り処理だけです。

読み取りを Sorsa API に移しても、X への投稿は続けられますか

はい。投稿、返信、DM には公式 X API を残し、読み取りを Sorsa API に送ります。Sorsa API は設計上、読み取り専用です。そのキーは X のどのアカウントにも操作できず、X のログイン情報も不要なので、両者は衝突せずに共存します。書き込み処理がまだ OAuth 1.0a のユーザートークンで署名しているなら、X は2026年9月21日から、アプリがそれを OAuth 2.0 トークンに交換できるようにしています。

先に v1.1 から v2 へ移行する必要がありますか

いいえ。v1.1 の読み取りを今も呼び出しているコードは、Sorsa API に直接対応させられます。statuses/user_timeline は POST /user-tweets、statuses/show は POST /tweet-info、users/show は GET /info になります。X は v1.1 をサポート限定のレガシーと位置づけ、2023年に v1.1 の検索を非推奨にしました。v2 で止まると、再び移行する前に OAuth 2.0 と従量課金に合わせた書き直しが必要になります。

旧 Basic と旧 Pro の X API プランはどうなりましたか

X はそれらを従量課金に移しました。旧 Basic のサブスクリプションは2026年6月1日以降の切り替え日に、Pro は2026年9月1日以降に終了しました。月払いの Pro アカウントには、切り替え時に$5,000のクレジットが請求され、自動チャージがオンになりました。読み取りは現在リソースごとの課金で、1投稿$0.005、1ユーザー$0.010です。同じツイートが、Sorsa API では検索で1,000件あたり$0.07からです。

#検証の方法

2026年11月2日に、X 自身の OpenAPI 仕様(バージョン2.173、200パス)と Sorsa API の公開リファレンス(45エンドポイント)を突き合わせてエンドポイントの対応表を作成し、v2 の演算子をすべて X の演算子一覧と、置き換え先をすべて Sorsa API の検索演算子と照合しました。同じ日に、X の変更履歴、料金ページ、ページネーションとレート制限のドキュメント、Python XDK のリファレンス、Tweepy 4.17.0 のクライアント、さらに検索インデックス、旧プラン、expansions 課金に関する X のフォーラムのお知らせを読みました。今回の確認では Sorsa API のキーがなかったため、掲載したレスポンスはデモの値です。Python と Node.js のコードブロックは、両 API のドキュメントどおりの形で応答するローカルのモックに対して実行しました。

Sorsa APIチーム 読み取り専用のX(Twitter)APIとスクレイピングツール、Sorsa APIを開発・運営しています。2022年から本番稼働し(旧TweetScout API)、累計60億リクエスト以上を処理しています。 Sorsa APIについて

変更点
  • 移行ガイドとして全面的に書き直しました。2023年から2026年の X の変更とそれぞれが壊すもの、両 API の仕様から作った v2 と Sorsa API のエンドポイント対応表、リクエスト変換ツール、フィールドと演算子の対応、Tweepy の移行前後のコード、ストリームの代わりのポーリング、シャドウラン(並行稼働)のテスト計画を扱っています。
  • 初公開。