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 の検索は、付いてくる投稿者の分まで課金されます。
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 アカウントに危険が及ぶことはありません。
キーを用意して最初のリクエストを送る
-
キーを取得する。 サインアップして、ダッシュボードから API キーをコピーします。最初の100リクエストは無料で、カード不要、有効期限もありません。
-
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 -
ツイートを検索する。 次を
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); -
ページを読む。 各レスポンスには最大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 の型 | 内容 |
|---|---|---|
id | string | 19桁のツイート ID。Number には変換しないこと |
full_text | string | 投稿の全文 |
created_at | string | UTC の ISO 8601。new Date(t.created_at) でパースできる |
likes_count, retweet_count, reply_count, quote_count | number | エンゲージメント数 |
view_count, bookmark_count | number | 表示数とブックマーク数 |
user | object | 投稿者のプロフィール。username や followers_count など19フィールド |
quoted_status, retweeted_status | object または存在しない | 引用元またはリポスト元のツイート(同じ形式) |
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: 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 は受け取ったとおりに送り返し、レスポンスに含まれなくなったら止めます。このループには非同期ジェネレーターがよく合います。ページを読むコード側からは、カーソルがまったく見えなくなるからです。
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 };
}
}
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);
}
検索とタイムラインは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リクエストです。
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チェーンを走らせます。
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 になり、バックオフで何秒も無駄にします。
サーバーレス環境では、このルールが気づかないうちに破られます。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 を待ちます。
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: 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 の最低レートでのコストを表示します。
// 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(新しいタブで開きます) はリクエストとしてカウントされません。
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 のメソッド | エンドポイント | 変わる点 |
|---|---|---|
search | POST /search-tweets | 直近7日間だけでなく、2006年までさかのぼれる |
searchAll | POST /search-tweets | 同じエンドポイントで、クエリに since: と until: を指定 |
userTimeline | POST /user-tweets | ユーザー名だけでよく、3,200件の上限がない |
userByUsername | GET /info(新しいタブで開きます) | すべてのプロフィールフィールドを返し、user.fields は不要 |
usersByUsernames | GET /info-batch | 1リクエストあたり100ユーザー名 |
tweets | POST /tweet-info-bulk | 1リクエストあたり100件の ID またはリンク |
followers | GET /followers | 1ページあたり200フォロワー |
tweetRetweetedBy | POST /retweeters(新しいタブで開きます) | 同じカーソルのループ |
tweet | なし | 読み取り専用:投稿は公式 X API で |
search- エンドポイント
POST /search-tweets- 変わる点
- 直近7日間だけでなく、2006年までさかのぼれる
searchAll- エンドポイント
POST /search-tweets- 変わる点
- 同じエンドポイントで、クエリに
since:とuntil:を指定
userTimeline- エンドポイント
POST /user-tweets- 変わる点
- ユーザー名だけでよく、3,200件の上限がない
userByUsername- エンドポイント
GET /info(新しいタブで開きます)- 変わる点
- すべてのプロフィールフィールドを返し、
user.fieldsは不要
usersByUsernames- エンドポイント
GET /info-batch- 変わる点
- 1リクエストあたり100ユーザー名
tweets- エンドポイント
POST /tweet-info-bulk- 変わる点
- 1リクエストあたり100件の ID またはリンク
followers- エンドポイント
GET /followers- 変わる点
- 1ページあたり200フォロワー
tweetRetweetedBy- エンドポイント
POST /retweeters(新しいタブで開きます)- 変わる点
- 同じカーソルのループ
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 のリリース表を確認しました。
情報源
- X API pay-per-usage pricing (docs.x.com)(新しいタブで開きます)
- X API PPU: expansions update (X Developers, October 23, 2026)(新しいタブで開きます)
- Tools and libraries: the official TypeScript SDK (docs.x.com)(新しいタブで開きます)
- Timelines (docs.x.com)(新しいタブで開きます)
- Search Posts (docs.x.com)(新しいタブで開きます)
- twitter-api-v2 on npm(新しいタブで開きます)
- @xdevplatform/xdk on npm(新しいタブで開きます)
- Node.js releases(新しいタブで開きます)
- Sorsa API reference, OpenAPI
- Sorsa API rate limits(新しいタブで開きます)
- Sorsa API pagination(新しいタブで開きます)