本文へスキップ

Node.js での Twitter API:fetch で動くコード付き 2026年版ガイド

Node.js なら標準の fetch で Twitter/X のデータを読めます。ヘッダーに API キーを付けて HTTPS リクエストを送り、JSON をパースし、返ってきたカーソルを次のページ取得に渡すだけです。公式 X API は投稿の読み取り1件につき$0.005かかります。

結論: Node.js で X のデータを読むのにパッケージは要りません。fetch が標準で入っています。公式 X API は twitter-api-v2 経由でも、開発者アカウントと前払いクレジットが必要です。料金は投稿1件につき$0.005、2026年11月22日からは投稿者を含めると$0.015です。公開データの取得なら Sorsa API を使います。ApiKey ヘッダー1つ、1リクエスト20ツイート、1,000件あたり$0.07から。100回分の無料リクエストで2,000ツイートを取得できます。

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

#2026年に公式 X API で使える npm パッケージは?

Node.js から公式 X API を呼べるパッケージは2つあります。コミュニティ製クライアントの twitter-api-v2 は、2026年10月25日にバージョン 1.31.0 に到達し、週に約33万回インストールされています。X 公式の TypeScript SDK @xdevplatform/xdk は 0.6.6 です。どちらも同じ有料プラットフォームのラッパーなので、開発者アカウント、ベアラートークン、前払いクレジットなしでは動きません。Tweepy と xdk を使った Python での同じリクエストは、Python での Twitter API のガイドにあります。

古い Node.js チュートリアルが飛ばしているのがクレジットの話です。X には新規開発者向けの無料プランがなく、レスポンスが返すオブジェクトごとに課金します。投稿は1件$0.005、ユーザーは1件$0.010で、投稿の読み取りは月300万件までです(X の料金表(新しいタブで開きます))。2026年11月22日からは、展開(expansions)で付随して取得される投稿者やメディアも、直接読み取った場合と同じ料金になります(10月23日付の X の告知(新しいタブで開きます))。そうなると、典型的な twitter-api-v2 の検索は、付いてくる投稿者の分まで課金されます。

twitter-api-v2
import { TwitterApi } from "twitter-api-v2";

const client = new TwitterApi("YOUR_BEARER_TOKEN").readOnly;
const page = await client.v2.search("nodejs lang:en", {
  max_results: 100,
  expansions: ["author_id"],
});
// 100 posts: $0.50. From Nov 22, 2026, with authors: up to $1.50

ページネーターにはもう1つ罠があります。ライブラリ自身のドキュメントによると、ページネーターに対する for await ループは、結果かレート制限が尽きるまで取得を続け、すべてのページが課金されます。料金、上限、日付の一覧は X API の料金にまとめています。

公式ルートで最初の呼び出しの前に必要なもの

  • console.x.com の開発者アカウント。X の開発者契約に同意した X アカウントで開設します。
  • アプリ。そのベアラートークンは X が一度しか表示しません。
  • 事前に購入するクレジット。返された投稿、ユーザー、メディアのオブジェクトごとに消費されます。
  • 投稿、いいね、フォローを行う処理のための OAuth ユーザートークン。

公開されている投稿やプロフィールの読み取りに、これらは一切不要です。独自のキーを持つ X データ API は素の HTTPS で応答し、Node の fetch だけでクライアントとして足ります。

#キー1つで Node.js からツイートを取得する

Node.js から X データを読むには、Twitter(X)API 兼スクレイピングツールである Sorsa API を使います。OAuth の代わりに ApiKey ヘッダー1つ、無料アカウントを含む全プランで毎秒20リクエスト、検索結果は1,000ツイートあたり$0.07からです。どのツイートにも投稿者のプロフィール全体が付いてくるので、コード側で expansions やフィールド一覧を指定する必要はありません。Sorsa API が X へのログインを求めることはないため、あなたの X アカウントに危険が及ぶことはありません。

キーを用意して最初のリクエストを送る

  1. キーを取得する。 サインアップして、ダッシュボードから API キーをコピーします。最初の100リクエストは無料で、カード不要、有効期限もありません。

  2. Node を確認してキーを保存する。 node -v を実行します。2026年11月時点の LTS は 22、24、26 で、3つとも fetch と --env-file が標準で使えます。キーは git が無視する .env ファイルに置き、API の呼び出しはサーバー側のコードからだけ行ってください。

    Terminal
    node -v                                  # v22 or newer
    echo 'SORSA_API_KEY=your_key' > .env
    echo '.env' >> .gitignore
    
  3. ツイートを検索する。 次を first.mjs として保存し、node --env-file=.env first.mjs で実行します。X の検索構文のクエリを、ApiKey ヘッダーにキーを付けて POST /search-tweets(新しいタブで開きます) に送ります。拡張子 .mjs により、トップレベルの await が有効になります。

    const API = "https://api.sorsa.io/v3";
    const KEY = process.env.SORSA_API_KEY ?? "YOUR_API_KEY";
    
    const res = await fetch(`${API}/search-tweets`, {
      method: "POST",
      headers: { ApiKey: KEY, "Content-Type": "application/json" },
      body: JSON.stringify({ query: "nodejs lang:en", order: "latest" }),
    });
    if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).message}`);
    
    const { tweets, next_cursor } = await res.json();
    for (const t of tweets) {
      console.log(t.user.username, t.full_text.slice(0, 60));
    }
    console.log("next page:", next_cursor);
    
  4. ページを読む。 各レスポンスには最大20ツイートと、次のページ用の next_cursor が入っています。以下の値は、ドキュメントどおりの形式のデモデータです。

    Response200 OK
    {
      "tweets": [
        {
          "id": "1985432176540981234",
          "full_text": "Moved our nightly X report to one Node script.",
          "created_at": "2026-11-01T18:42:07Z",
          "lang": "en",
          "likes_count": 58,
          "retweet_count": 9,
          "reply_count": 4,
          "quote_count": 1,
          "view_count": 4920,
          "bookmark_count": 6,
          "is_reply": false,
          "user": {
            "id": "1592041833471827968",
            "username": "node_stream_digest",
            "display_name": "Node Stream Digest",
            "followers_count": 2310,
            "verified": false
          }
        }
      ],
      "next_cursor": "DAABCgABF7Y8kQ3wAAA"
    }
    

次の表の各フィールドはレスポンスにそのまま入っています。JavaScript の型は res.json() が返す型で、ID はあえて文字列のままにしてあります。

フィールドJavaScript の型内容
idstring19桁のツイート ID。Number には変換しないこと
full_textstring投稿の全文
created_atstringUTC の ISO 8601。new Date(t.created_at) でパースできる
likes_count, retweet_count, reply_count, quote_countnumberエンゲージメント数
view_count, bookmark_countnumber表示数とブックマーク数
userobject投稿者のプロフィール。username や followers_count など19フィールド
quoted_status, retweeted_statusobject または存在しない引用元またはリポスト元のツイート(同じ形式)
  • id

    JavaScript の型
    string
    内容
    19桁のツイート ID。Number には変換しないこと
  • full_text

    JavaScript の型
    string
    内容
    投稿の全文
  • created_at

    JavaScript の型
    string
    内容
    UTC の ISO 8601。new Date(t.created_at) でパースできる
  • likes_count, retweet_count, reply_count, quote_count

    JavaScript の型
    number
    内容
    エンゲージメント数
  • view_count, bookmark_count

    JavaScript の型
    number
    内容
    表示数とブックマーク数
  • user

    JavaScript の型
    object
    内容
    投稿者のプロフィール。username や followers_count など19フィールド
  • quoted_status, retweeted_status

    JavaScript の型
    object または存在しない
    内容
    引用元またはリポスト元のツイート(同じ形式)

#429 とサーバーエラーを1つのヘルパーでリトライする

すべての呼び出しを、リトライする価値のあるものだけを再試行する1つの関数に通します。429 は、あなたのキーで1秒以内に20を超えるリクエストを開始したという意味です。5xx やタイムアウトは一時的な障害です。このガイドの以降のコードは、この sorsa ヘルパーをインポートして使います。

sorsa.mjs
// sorsa.mjs: one place for the key, timeouts and retries
const API = "https://api.sorsa.io/v3";
const KEY = process.env.SORSA_API_KEY ?? "YOUR_API_KEY";
const RETRY = new Set([429, 500, 502, 503, 504]);
const sleep = (ms) => new Promise((ok) => setTimeout(ok, ms));

export async function sorsa(path, { body, query } = {}) {
  const url = new URL(API + path);
  if (query) url.search = query; // a URLSearchParams for GET endpoints

  for (let attempt = 0; ; attempt++) {
    let res;
    try {
      res = await fetch(url, {
        method: body ? "POST" : "GET",
        headers: { ApiKey: KEY, "Content-Type": "application/json" },
        body: body && JSON.stringify(body),
        signal: AbortSignal.timeout(30_000),
      });
    } catch (err) { // timeout or dropped connection
      if (attempt === 4) throw err;
      await sleep(1000 * 2 ** attempt);
      continue;
    }
    if (res.ok) return res.json();
    if (!RETRY.has(res.status) || attempt === 4) {
      const { message } = await res.json().catch(() => ({}));
      throw new Error(`${res.status} on ${path}: ${message}`);
    }
    await sleep(1000 * 2 ** attempt); // 1, 2, 4, 8 seconds
  }
}

401 や 403 は決してリトライしません。キーが間違っているか未指定であるか、リクエストを使い切っているかのどれかで、どれなのかは message フィールドに書かれています。失敗したリクエストは課金されないので、リトライに費用はかかりません。Sorsa API のエンドポイントはすべて読み取りだけを行うため、/search-tweets のような POST を繰り返しても、何かが二重に作られることはありません。Sorsa API はウィンドウが毎秒リセットされるため、レート制限用のヘッダーを返しません。1秒待てば最初の 429 は解消します。

#next_cursor でページをたどる

カーソルは次のページへの不透明なキーです。next_cursor は受け取ったとおりに送り返し、レスポンスに含まれなくなったら止めます。このループには非同期ジェネレーターがよく合います。ページを読むコード側からは、カーソルがまったく見えなくなるからです。

pages.mjs
import { sorsa } from "./sorsa.mjs";

export async function* pages(path, body, maxPages = 100) {
  for (let n = 0; n < maxPages; n++) {
    const page = await sorsa(path, { body });
    yield page;
    if (!page.next_cursor) return; // null, empty or absent: the end
    body = { ...body, next_cursor: page.next_cursor };
  }
}
search.mjs
import { pages } from "./pages.mjs";

const search = { query: "nodejs lang:en", order: "latest" };
for await (const page of pages("/search-tweets", search, 50)) {
  for (const t of page.tweets) console.log(t.id, t.created_at);
}
自分のコード for await ページ1をyield ページ2をyield return クエリ query + "c1" query + "c2" リクエスト ページ ページ1 ページ2 ページ3 Sorsa API POST /search-tweets ツイート20件 next_cursor: "c1" ツイート17件 next_cursor: "c2" ツイート12件 next_cursor: null
ジェネレーターは各カーソルを送り返し、null で止まる。途中のページが短くても終わりとは限らない。

検索とタイムラインは1ページあたり最大20ツイートを返しますが、まだ続きがあってもページの件数が少ないことがあります。そのため、件数で終わりを判断してはいけません。1つのカーソルをたどる間は、クエリと order を変えないでください。maxPages は予算として扱いましょう。クエリが何件に一致しても、50ページは50リクエストです。GET エンドポイントはカーソルをクエリ文字列で受け取ります。GET /followers(新しいタブで開きます) は1ページあたり最大200プロフィールを返し、query.set("next_cursor", page.next_cursor) で先に進みます。POST /user-tweets(新しいタブで開きます) も同じジェネレーターで動きます。再開できるカーソルでユーザーの全ツイートをダウンロードするには、そのガイドに完全なスクリプトがあります。

#1リクエストで100件のツイートやプロフィールを取得する

ID やユーザー名をすでに持っているなら、バッチで送ります。POST /tweet-info-bulk(新しいタブで開きます) は tweet_links に最大100件のツイート ID またはリンクを受け取り、GET /info-batch(新しいタブで開きます) は最大100件のユーザー名を受け取ります。バッチ1回が1リクエストです。

batch.mjs
import { sorsa } from "./sorsa.mjs";
// tweetIds and usernames: arrays of strings you already hold

const chunks = (list, size = 100) =>
  Array.from({ length: Math.ceil(list.length / size) },
    (_, i) => list.slice(i * size, (i + 1) * size));

const tweets = [];
for (const ids of chunks(tweetIds)) {
  const body = { tweet_links: ids }; // up to 100 IDs or links
  tweets.push(...(await sorsa("/tweet-info-bulk", { body })).tweets);
}

const users = [];
for (const names of chunks(usernames)) {
  const query = new URLSearchParams();
  for (const name of names) query.append("usernames", name);
  users.push(...(await sorsa("/info-batch", { query })).users);
}

GET /info-batch は usernames を繰り返しパラメーター(usernames=a&usernames=b)として読むので、append で組み立てます。new URLSearchParams({ usernames }) に配列を渡すと、名前がカンマで連結されて1つの値になってしまいます。リクエストは何を返しても同じ料金なので、100ツイートのバッチも1ツイートと同じコストです。1,000ツイートあたり$0.014から、公式 X API の$5.00の250分の1以下です。バッチのレスポンスにカーソルはありません。バッチ全体が tweets または users に入っています。

#1つのキーで毎秒20リクエストを実行する

Sorsa API はキー1つにつき毎秒20リクエストを全プランで許可しており、Node のプロセス1つで簡単にそこまで届きます。イベントループは、1つのスレッドで数十のリクエストを同時に処理できるからです。守るべきなのは開始レートなので、すべての呼び出しの前に1つのペーサーを置き、作業を独立したチェーンに分けます。1つのカーソルチェーンは並列化できません。各ページが直前のページのカーソルを必要とするからです。そこで、アカウントごと、または検索の日ごとに1チェーンを走らせます。

lanes.mjs
import { sorsa } from "./sorsa.mjs";

// Hands out start times 1/19 s apart, however many lanes are waiting.
function pacer(perSecond) {
  const gap = 1000 / perSecond;
  let next = 0;
  return () => {
    const now = Date.now();
    const start = Math.max(next, now);
    next = start + gap;
    return new Promise((ok) => setTimeout(ok, start - now));
  };
}

const slot = pacer(19); // one pacer per key, one start under the limit

async function lane(query, maxPages = 100) {
  const tweets = [];
  let body = { query };
  for (let n = 0; n < maxPages; n++) {
    await slot();
    const page = await sorsa("/search-tweets", { body });
    tweets.push(...page.tweets);
    if (!page.next_cursor) break;
    body = { ...body, next_cursor: page.next_cursor };
  }
  return tweets;
}

const day = (d) => new Date(Date.UTC(2026, 9, d)).toISOString().slice(0, 10);
const queries = Array.from({ length: 31 }, (_, i) =>
  `nodejs lang:en since:${day(i + 1)} until:${day(i + 2)}`); // Oct 1-31
const results = await Promise.all(queries.map((q) => lane(q)));
console.log(results.flat().length, "tweets");

ペーサーにロックは要りません。JavaScript は一度に1つのコールバックしか実行しないので、2つのレーンが同じ開始時刻を取ることはありません。毎秒19回の開始にしておくと、開始1回分の余裕が残ります。タイマーのジッターで20回目の開始が次のウィンドウに入ってしまうことがあるためです。それでもすり抜けた 429 は、ヘルパーのリトライが拾います。よくある間違いは、ペーサーなしの Promise.all です。60件の呼び出しを一度に発火すると、そのうち40件がそのまま 429 になり、バックオフで何秒も無駄にします。

Node.jsプロセス1つ pacer(19) 10/1 10/2 10/3 10/4 APIキー1つ 毎秒19回開始 キーあたり上限20回を下回る サーバーレスのインスタンス3つ pacer(19) pacer(19) pacer(19) それぞれ自分の開始分しか数えない 同じAPIキー 毎秒57回開始 うち37回が429で待機
ペーサーが数えるのは自分のプロセスの開始だけ。キー1つにペーサーを1つ置くか、インスタンス間でレートを分け合うこと。

サーバーレス環境では、このルールが気づかないうちに破られます。Lambda、Vercel、Cloud Run では、関数のインスタンスごとに lanes.mjs が自分のコピーとして読み込まれます。ウォーム状態のインスタンスが3つあれば、1つのキーに対して3つのペーサーが動き、合計で毎秒最大57リクエストを開始してしまいます。各インスタンスにレートの取り分を割り当てる(3つなら pacer(6))か、大量処理を1つのワーカーで実行してください。上限はキー単位でカウントされるので、2つ目のワーカーに2つ目のキーを使えば、そのキーに毎秒20の枠が付いてきます。毎秒20リクエストなら、100件のバッチで毎秒最大2,000ツイート、検索ページで400ツイートを処理できます。

#ツイートを JSONL か CSV に保存する

すべてのツイートをメモリに溜めるのではなく、ページが届くたびに書き出します。1行に1つの JSON オブジェクトを置く JSON Lines は、全フィールドを保持でき、安全に追記できます。CSV は Excel や Google スプレッドシートで開けます。下の2つのストリームは、ディスクが追いつかないときに drain を待ちます。

save.mjs
import { createWriteStream } from "node:fs";
import { once } from "node:events";
import { pages } from "./pages.mjs";

const jsonl = createWriteStream("tweets.jsonl");
const csv = createWriteStream("tweets.csv");
const put = async (out, line) => {
  if (!out.write(line)) await once(out, "drain"); // respect backpressure
};
const cell = (v) => `"${String(v ?? "").replaceAll('"', '""')}"`;
const BOM = String.fromCharCode(0xfeff); // tells Excel the file is UTF-8

await put(csv, BOM + "id,created_at,username,likes_count,full_text\r\n");
const search = { query: "nodejs lang:en" };
for await (const page of pages("/search-tweets", search, 500)) {
  for (const t of page.tweets) {
    await put(jsonl, JSON.stringify(t) + "\n");
    const row = [t.id, t.created_at, t.user.username, t.likes_count,
      t.full_text];
    await put(csv, row.map(cell).join(",") + "\r\n");
  }
}
jsonl.end();
csv.end();

CSV の先頭にあるバイトオーダーマークは、ファイルが UTF-8 であることを Excel に伝えるので、絵文字やアクセント付きの文字も正しく表示されます。すべてのセルを引用符で囲むため、ツイート内のカンマ、引用符、改行が別の列に漏れることはありません。500ページで最大1万ツイート、リクエストは500回です。実行がどれだけ長くなってもメモリ使用量は一定です。

知っておきたいこと

JavaScript の数値が整数を正確に保持できるのは 2^53(約9千兆)までですが、ツイート ID は19桁あります。Number("1985432176540981234") は 1985432176540981200 を返し、これは別のツイートです。Sorsa API はすべての ID とカーソルを文字列で返します。文字列のまま保持し、文字列として比較し、ID で並べ替えたいときは BigInt(id) を使ってください。

#レスポンスに TypeScript の型を付ける

TypeScript なら、API のフィールド名に正確に合わせたいくつかのインターフェースで型安全になります。以下は現行の OpenAPI リファレンスに従ったもので、ほとんどのコードが読むフィールドをカバーしています。残りはリファレンスを参照してください。

sorsa-types.ts
// sorsa-types.ts: field names as in the Sorsa API reference
export interface SorsaUser {
  id: string; // 64-bit ID: keep it a string
  username: string;
  display_name: string;
  description: string;
  followers_count: number;
  followings_count: number;
  tweets_count: number;
  verified: boolean;
  protected: boolean;
  created_at: string; // ISO 8601
}

export interface SorsaTweet {
  id: string;
  full_text: string;
  created_at: string;
  lang: string;
  likes_count: number;
  retweet_count: number;
  reply_count: number;
  quote_count: number;
  view_count: number;
  bookmark_count: number;
  is_reply: boolean;
  user: SorsaUser;
  quoted_status?: SorsaTweet;
  retweeted_status?: SorsaTweet;
}

export interface TweetsPage {
  tweets: SorsaTweet[];
  next_cursor?: string | null;
}

オプションのフィールドには ? が付きます。引用元やリポスト元のツイートは引用とリポストにしか存在しないので、コンパイラが読む前のチェックを強制します。境界で const page = (await sorsa(path, { body })) as TweetsPage とキャストすれば、以降のコードは型チェックされます。Node 22.18 以降は型を取り除いて .ts ファイルを直接実行できるので、ビルド手順なしで node --env-file=.env job.ts が動きます。全エンドポイントをまとめて扱うなら、openapi-typescript のような OpenAPI ジェネレーターで、リファレンス全体を型に変換できます。

#ブラウザの JavaScript から Twitter API を呼べる?

呼べません。ブラウザのコードに入れた API キーは、開発者ツールを開けば誰でも読めてしまいます。Sorsa API はサーバー側からの呼び出し用に作られているため、ブラウザからは CORS エラーになります。サーバーに小さなルートを置いてキーはそこに保管し、フロントエンドからはそのルートを呼びます。

// server.mjs: npm install express
import express from "express";
import { sorsa } from "./sorsa.mjs";

const app = express();
app.get("/api/tweets", async (req, res) => {
  const query = String(req.query.q ?? "").slice(0, 200);
  const page = await sorsa("/search-tweets", { body: { query } });
  res.json(page.tweets.map((t) => ({ id: t.id, text: t.full_text })));
});
app.listen(3000);

ブラウザ側では、自分のドメインに対して fetch("/api/tweets?q=nodejs") を実行するだけで、キーは目にしません。このパターンはどのサーバーランタイムにも合います。Cloudflare Worker や Lambda 関数は環境変数にキーを持ち、ページはそこにしか話しかけません。多くの訪問者が同じ内容を求めるなら、ルートの応答を1分間キャッシュしてください。Sorsa API はリクエストのたびに最新のデータを返すので、1回のキャッシュされた応答が、1リクエストのコストで全員に行き渡ります。

#Node.js のジョブのコスト

Sorsa API はエンドポイントが何を返すかにかかわらず、リクエスト単位で課金します。検索結果は1,000ツイートあたり$0.07から、バッチは$0.014からで、失敗したリクエストは課金されません。公式 X API はオブジェクト単位で課金します。1,000投稿あたり$5.00で、2026年11月22日からは、ほとんどの twitter-api-v2 の検索のように各投稿の投稿者を展開すると、1,000投稿あたり最大$15.00になります。

検索で収集するツイート数

公式 X API

$5,000

1Mツイートの場合

1投稿$0.005

Sorsa API

$70から

1Mツイートの場合

1,000件あたり$0.07から

各ツイートに投稿者のプロフィールが付いてきます

下のビルダーは、ジョブ用の Node.js ループを書き出し、必要なリクエスト数、1つのキーでの最短時間、Sorsa API の最低レートでのコストを表示します。

ジョブ
件数
job.mjs
// sorsa()とpages()は上のヘルパー関数から読み込みます
const body = { query: "nodejs lang:en" };
for await (const page of pages("/search-tweets", body, 5000)) {
  save(page.tweets); // 1ページあたり最大20ツイート
}
リクエスト数
5,000
1キーでの最短時間
約4分
Sorsa API
最安 $7

時間は毎秒20リクエスト、費用は1,000リクエストあたり$1.40で計算しています。検索は1本のカーソルの連なりなので、日付範囲に分割して並行実行してください。

表示している時間は1キーで出せる最短です。1本のカーソルの連なりは往復の速さで進むため、大きなジョブはレーンに分割してください。

1,000リクエストあたりの単価は、Starter の$4.90から Hyperscale の$1.40までで、すべてのエンドポイントで同じ単価です。残りの確認は無料で行えます。GET /key-usage-info(新しいタブで開きます) はリクエストとしてカウントされません。

usage.mjs
import { sorsa } from "./sorsa.mjs";
const usage = await sorsa("/key-usage-info");
console.log(usage.remaining_requests, "left until", usage.valid_until);

#twitter-api-v2 のコードを Sorsa API へ移す

twitter-api-v2 の呼び出しの大半は Sorsa API のエンドポイント1つに対応し、すべてのツイートが最初から投稿者とカウントを持つため、コードから expansions とフィールド一覧がなくなります。次の表は、よく使う client.v2 のメソッドとエンドポイントを対応させたものです。コードベース全体で Twitter/X API から移行するなら、そのガイドが v2 の全エンドポイントとレスポンスのフィールドを対応づけています。

twitter-api-v2 のメソッドエンドポイント変わる点
searchPOST /search-tweets直近7日間だけでなく、2006年までさかのぼれる
searchAllPOST /search-tweets同じエンドポイントで、クエリに since: と until: を指定
userTimelinePOST /user-tweetsユーザー名だけでよく、3,200件の上限がない
userByUsernameGET /info(新しいタブで開きます)すべてのプロフィールフィールドを返し、user.fields は不要
usersByUsernamesGET /info-batch1リクエストあたり100ユーザー名
tweetsPOST /tweet-info-bulk1リクエストあたり100件の ID またはリンク
followersGET /followers1ページあたり200フォロワー
tweetRetweetedByPOST /retweeters(新しいタブで開きます)同じカーソルのループ
tweetなし読み取り専用:投稿は公式 X API で
  • search

    エンドポイント
    POST /search-tweets
    変わる点
    直近7日間だけでなく、2006年までさかのぼれる
  • searchAll

    エンドポイント
    POST /search-tweets
    変わる点
    同じエンドポイントで、クエリに since: と until: を指定
  • userTimeline

    エンドポイント
    POST /user-tweets
    変わる点
    ユーザー名だけでよく、3,200件の上限がない
  • userByUsername

    変わる点
    すべてのプロフィールフィールドを返し、user.fields は不要
  • usersByUsernames

    エンドポイント
    GET /info-batch
    変わる点
    1リクエストあたり100ユーザー名
  • tweets

    エンドポイント
    POST /tweet-info-bulk
    変わる点
    1リクエストあたり100件の ID またはリンク
  • followers

    エンドポイント
    GET /followers
    変わる点
    1ページあたり200フォロワー
  • tweetRetweetedBy

    変わる点
    同じカーソルのループ
  • tweet

    エンドポイント
    なし
    変わる点
    読み取り専用:投稿は公式 X API で

フィールド名も変わります。text は full_text に、public_metrics.like_count は likes_count に、author_id と includes.users は user オブジェクトに、meta.next_token は next_cursor になります。ページネーターの fetchNext() は上の pages ジェネレーターに置き換わり、レート制限ではなく自分で決めたページ予算で止まります。POST /user-tweets は、公式のタイムラインが止まる最新3,200件の投稿よりも先まで届きます(X のタイムラインのドキュメント(新しいタブで開きます))。また、公式 X API の直近検索は過去7日間しかカバーしません(X の検索のドキュメント(新しいタブで開きます))。

#今日、最初の Node.js リクエストを実行する

Node.js から X データを読むのに必要なのは、fetch、キー1つ、そして4つの小さな部品です。リトライ用の sorsa、カーソル用の pages、100件のバッチ用の chunks、毎秒20リクエスト用のペーサーです。これを始めるなら、Sorsa API が最初の Twitter API です。検索は1リクエスト20ツイートで1,000件あたり$0.07から、すべてのツイートに投稿者が含まれ、検索は2006年までさかのぼれて、100回分の無料リクエストはカード不要で期限もありません。コードなしで同じデータをワークフローに取り込むには、n8n での Twitter API を参照してください。

#よくある質問

Node.js ではどの Twitter API を使うべきですか?

Node.js から X データを読むなら Sorsa API を使います。標準の fetch に ApiKey ヘッダーのキー1つで、検索結果は1,000ツイートあたり$0.07から、バッチは$0.014から、全プランで毎秒20リクエスト、検索は2006年までさかのぼれます。X へのログインは不要なので、あなたのアカウントが危険にさらされることはありません。投稿も行いたい場合は、公式 X API と組み合わせてください。

Node.js 向けの公式 Twitter API SDK はありますか?

あります。X は TypeScript SDK の @xdevplatform/xdk(バージョン 0.6.6)を公開しており、Node.js とバンドラーで動きます。ただし公式 X API のクライアントなので、呼び出しのたびに開発者アカウント、ベアラートークン、投稿1件$0.005の前払いクレジットが必要です。Sorsa API に SDK は不要です。素の fetch で、45あるエンドポイントすべてを呼べます。

Twitter/X データ向けの最良の npm パッケージは何ですか?

Sorsa API なら、パッケージは不要です。Node の標準の fetch と、このガイドの40行ほどのヘルパーで全エンドポイントをカバーでき、更新すべき依存関係もありません。公式 X API なら、最もインストールされているクライアントは twitter-api-v2 で、週に約33万回のダウンロード、バージョンは 1.31.0 です。X 自身の @xdevplatform/xdk もあり、どちらもオブジェクトごとに X のクレジットを課金します。

API キーなしでツイートを取得できる無料の npm パッケージはありますか?

rettiwt-api のようなパッケージは、API ではなく X のウェブアプリを読み取ります。ログインなしで使えるのは一部の取得処理だけで、それ以外はリクエストのたびに自分の X アカウントの Cookie を送ります。そのため通信があなたのアカウントに結び付き、X がウェブアプリを変更すると動かなくなります。Sorsa API は X のログインを一切求めず、100回分の無料リクエストにカードは不要です。トレードオフは Twitter のスクレイピング方法で比較しています。

Sorsa API で Node.js からツイートを投稿できますか?

できません。Sorsa API は設計上、読み取り専用です。公開された投稿、プロフィール、フォロワーを読み取るだけで、投稿、いいね、フォロー、メッセージ送信は行わないため、キーで X アカウントを操作することはできません。Node.js から投稿するには、OAuth ユーザートークンを使った公式 X API を使います。たとえば twitter-api-v2 の client.v2.tweet は1投稿$0.015です。読み取りには Sorsa API を使い続けてください。

開発者アカウントなしで Node.js から Twitter データを取得できますか?

できます。Sorsa API に必要なのは自社のキーだけです。サインアップして ApiKey ヘッダーを設定すれば、Node.js からどのエンドポイントでも呼べます。X の開発者アカウントも、アプリの審査も、OAuth も不要です。最初の100リクエストは無料で、検索なら最大2,000ツイート、100件のバッチなら1万件を取得できます。選択肢の比較は 開発者アカウントなしの Twitter API にあります。

#検証方法

すべてのリクエストを、Sorsa API の最新の OpenAPI リファレンス(45エンドポイント)と、認証、レート制限、ページネーションに関するドキュメントから書きました。いずれも2026年11月2日に確認しています。今回は Sorsa API のキーがなかったため、レスポンスはデモ値です。Node.js のコードブロックは、ドキュメントどおりの形式で応答するローカルのモックに対して Node 26 で実行しました。最初のリクエスト、リトライヘルパー(503を2回返した後に60件のバーストを送信)、カーソルのジェネレーター、100件のバッチ、JSONL と CSV の書き出し、キー使用状況の呼び出し、ペーサーです。毎秒19回の開始では、ペーサーは31レーン93リクエストを処理し、3回の実行で 429 は一度も出ませんでした。ちょうど20回にすると、3回中1回の実行で29件のリクエストがリトライ側に送られました。Express、Next.js、twitter-api-v2 のスニペットは実行していません。同じ日に、docs.x.com の X の料金、expansions に関する11月22日の告知、ツールのページ、twitter-api-v2 と @xdevplatform/xdk の npm レジストリのエントリ、Node.js のリリース表を確認しました。

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

変更点
  • 全面的に書き直しました。11月22日開始の展開(expansions)課金を含む現行の公式 X API の料金、twitter-api-v2 1.31.0 と X の TypeScript SDK 0.6.6、そしてキー1つで使う Node.js の方法を反映しています。リトライ、カーソル用の非同期ジェネレーター、100件のバッチ、毎秒20リクエストのペーサーとサーバーレスの落とし穴、JSONL と CSV、TypeScript の型、Express と Next.js のルート、twitter-api-v2 のメソッド対応表を扱います。
  • 初公開。