API de Twitter en Node.js: guía 2026 con código funcional en fetch
Node.js lee datos de Twitter/X con su fetch integrado: envías una solicitud HTTPS con una clave de API en un encabezado, procesas el JSON y devuelves el cursor recibido para pedir la página siguiente. La API oficial de X cobra $0.005 por cada post leído.
Respuesta corta: Node.js no necesita ningún paquete para leer datos de X: fetch viene integrado. La API oficial de X, incluso con twitter-api-v2, exige una cuenta de desarrollador y créditos prepagados: $0.005 por post, y $0.015 con su autor a partir del 22 de noviembre de 2026. Para datos públicos, usa Sorsa API: un solo encabezado ApiKey, 20 tweets por solicitud desde $0.07 por 1,000; con las 100 solicitudes gratis obtienes 2,000.
- Endpoint
POST /search-tweets- Tamaño de página
- 20 tweets por solicitud
- Precio
- desde $0.07 por 1,000 tweets
#¿Qué paquete de npm funciona con la API oficial de X en 2026?
Dos paquetes llaman a la API oficial de X desde Node.js. twitter-api-v2, un cliente de la comunidad, llegó a la versión 1.31.0 el 25 de octubre de 2026 y se instala unas 336 mil veces por semana. El SDK de TypeScript de la propia X, @xdevplatform/xdk, está en la 0.6.6. Los dos envuelven la misma plataforma de pago, así que ninguno funciona sin una cuenta de desarrollador, un bearer token y créditos prepagados. Las mismas solicitudes en Python, con Tweepy y xdk en ese papel, están en la guía de la API de Twitter en Python.
Los créditos son lo que omiten los tutoriales viejos de Node. X no tiene un nivel gratuito para desarrolladores nuevos y cobra cada objeto que devuelve una respuesta: $0.005 por post y $0.010 por usuario, hasta 3 millones de lecturas de posts al mes (tabla de precios de X (se abre en una pestaña nueva)). A partir del 22 de noviembre de 2026, los autores y los medios que llegan mediante expansiones cuestan lo mismo que las lecturas directas (aviso de X del 23 de octubre (se abre en una pestaña nueva)). Una búsqueda típica con twitter-api-v2 paga entonces por cada autor que adjunta:
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
Sus paginadores añaden una segunda trampa: la propia documentación de la librería dice que un bucle for await sobre un paginador sigue pidiendo hasta que se acaban los resultados o tu límite de tasa, y cada página se cobra. Todas las tarifas, topes y fechas están en precios de la API de X.
Lo que exige la vía oficial antes de la primera llamada
- Una cuenta de desarrollador en console.x.com, abierta con una cuenta de X que acepte el Acuerdo para Desarrolladores de X.
- Una app, cuyo bearer token X muestra una sola vez.
- Créditos comprados por adelantado y gastados por cada objeto de post, usuario y medio que se devuelve.
- Tokens de usuario de OAuth para todo lo que publique, dé me gusta o siga cuentas.
Leer posts y perfiles públicos no necesita nada de eso. Una API de datos de X con su propia clave responde por HTTPS simple, y el fetch de Node es todo el cliente.
#Obtén tweets en Node.js con una sola clave de API
Para leer datos de X desde Node.js, usa Sorsa API, una API y scraper de Twitter (X): un solo encabezado ApiKey en lugar de OAuth, 20 solicitudes por segundo en todos los planes, también en las cuentas gratuitas, y resultados de búsqueda desde $0.07 por 1,000 tweets. Cada tweet llega con el perfil completo de su autor, así que el código no define expansiones ni listas de campos. Sorsa API nunca pide un inicio de sesión de X, de modo que ninguna cuenta tuya de X corre riesgo.
Configura la clave y envía la primera solicitud
-
Obtén una clave. Regístrate y copia tu clave de API desde el panel. Las primeras 100 solicitudes son gratis, no piden tarjeta y nunca vencen.
-
Revisa Node y guarda la clave. Ejecuta
node -v: las líneas LTS de noviembre de 2026 son la 22, la 24 y la 26, y las tres traenfetchy--env-fileintegrados. Pon la clave en un archivo.envque git ignore, y llama a la API solo desde código de servidor.Terminal node -v # v22 or newer echo 'SORSA_API_KEY=your_key' > .env echo '.env' >> .gitignore -
Busca tweets. Guarda esto como
first.mjsy ejecutanode --env-file=.env first.mjs. Envía una consulta con la sintaxis de búsqueda de X aPOST /search-tweets(se abre en una pestaña nueva) con la clave en el encabezadoApiKey; la extensión.mjsactiva elawaitde nivel superior.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); -
Lee la página. Cada respuesta trae hasta 20 tweets y un
next_cursorpara la página siguiente. Los valores de abajo son datos de demostración con la forma documentada.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" }
Cada campo de abajo sale directo de la respuesta; el tipo de JavaScript es el que te da res.json(), y los IDs siguen siendo strings a propósito.
| Campo | Tipo de JavaScript | Qué contiene |
|---|---|---|
id | string | El ID de 19 dígitos del tweet: nunca lo conviertas a Number |
full_text | string | El texto completo del post |
created_at | string | ISO 8601 en UTC: new Date(t.created_at) lo interpreta |
likes_count, retweet_count, reply_count, quote_count | number | Conteos de interacción |
view_count, bookmark_count | number | Vistas y guardados |
user | object | El perfil del autor: 19 campos como username y followers_count |
quoted_status, retweeted_status | object o ausente | El tweet citado o republicado, con la misma forma |
id- Tipo de JavaScript
- string
- Qué contiene
- El ID de 19 dígitos del tweet: nunca lo conviertas a Number
full_text- Tipo de JavaScript
- string
- Qué contiene
- El texto completo del post
created_at- Tipo de JavaScript
- string
- Qué contiene
- ISO 8601 en UTC:
new Date(t.created_at)lo interpreta
likes_count,retweet_count,reply_count,quote_count- Tipo de JavaScript
- number
- Qué contiene
- Conteos de interacción
view_count,bookmark_count- Tipo de JavaScript
- number
- Qué contiene
- Vistas y guardados
user- Tipo de JavaScript
- object
- Qué contiene
- El perfil del autor: 19 campos como
usernameyfollowers_count
quoted_status,retweeted_status- Tipo de JavaScript
- object o ausente
- Qué contiene
- El tweet citado o republicado, con la misma forma
#Reintenta los 429 y los errores del servidor en una sola función
Pon cada llamada detrás de una función que reintente solo lo que vale la pena reintentar. Un 429 significa que más de 20 solicitudes empezaron dentro de un mismo segundo con tu clave; un 5xx o un tiempo de espera agotado es una falla pasajera. El resto de esta guía importa esta función 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
}
}
Un 401 o un 403 nunca merece un reintento: la clave es incorrecta o falta, o sus solicitudes se agotaron, y el campo message dice cuál es el caso. Las solicitudes fallidas nunca se cobran, así que un reintento no cuesta nada. Todos los endpoints de Sorsa API solo leen, de modo que repetir un POST como /search-tweets no puede crear nada dos veces. Sorsa API no envía encabezados de límite de tasa porque la ventana se reinicia cada segundo: la primera espera de un segundo despeja un 429.
#Recorre los resultados con next_cursor
Un cursor es una clave opaca hacia la página siguiente: devuelve next_cursor tal como llegó y detente cuando una respuesta no traiga ninguno. Un generador asíncrono encaja bien en este bucle, porque el código que lee las páginas nunca ve el 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);
}
La búsqueda y los timelines devuelven hasta 20 tweets por página, y una página puede traer menos mientras quedan más, así que la cantidad nunca te dice el final. Mantén sin cambios la consulta y order a lo largo de un mismo cursor, y trata maxPages como un presupuesto: 50 páginas son 50 solicitudes, sin importar lo que coincida con la consulta. Los endpoints GET reciben el cursor en la cadena de consulta: GET /followers (se abre en una pestaña nueva) devuelve hasta 200 perfiles por página, y query.set("next_cursor", page.next_cursor) lo avanza. POST /user-tweets (se abre en una pestaña nueva) funciona con el mismo generador; para descargar todos los tweets de un usuario con un cursor reanudable, esa guía tiene el script completo.
#Obtén 100 tweets o perfiles por solicitud
Cuando ya tienes IDs o nombres de usuario, envíalos en lotes. POST /tweet-info-bulk (se abre en una pestaña nueva) acepta hasta 100 IDs o enlaces de tweets en tweet_links, y GET /info-batch (se abre en una pestaña nueva) acepta hasta 100 nombres de usuario; cada lote es una solicitud.
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 lee usernames como un parámetro repetido (usernames=a&usernames=b), así que constrúyelo con append. Pasar un arreglo a new URLSearchParams({ usernames }) une los nombres con comas en un solo valor. Una solicitud cuesta lo mismo sin importar lo que devuelva, así que un lote de 100 tweets cuesta lo que cuesta un tweet: desde $0.014 por 1,000 tweets, más de 250 veces menos que los $5.00 de la API oficial de X. Las respuestas por lote no traen cursor; el lote completo viene en tweets o en users.
#Ejecuta 20 solicitudes por segundo con una sola clave
Sorsa API permite 20 solicitudes por segundo por clave en todos los planes, y un solo proceso de Node lo alcanza con facilidad: el bucle de eventos mantiene decenas de solicitudes en vuelo en un solo hilo. El límite que hay que respetar es la tasa de inicio, así que pon un único regulador delante de cada llamada y divide el trabajo en cadenas independientes. Una cadena de cursor no puede correr en paralelo, porque cada página necesita el cursor de la anterior; corre entonces una cadena por cuenta o una por día de una búsqueda.
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");
El regulador no necesita ningún bloqueo: JavaScript ejecuta un callback a la vez, así que dos cadenas nunca pueden tomar el mismo instante de inicio. Con 19 inicios por segundo deja un inicio de margen, porque la variación de los temporizadores puede empujar el inicio número 20 a la ventana siguiente. La función de reintentos atrapa cualquier 429 que aun así se cuele. Promise.all sin regulador es el error más común: 60 llamadas lanzadas de golpe mandan 40 directo a 429 y pasan segundos en espera.
Las plataformas serverless rompen esa regla sin avisar. Cada instancia de función en Lambda, Vercel o Cloud Run carga su propia copia de lanes.mjs. Tres instancias activas ejecutan entonces tres reguladores sobre una misma clave y entre todas inician hasta 57 solicitudes por segundo. Dale a cada instancia una parte de la tasa (pacer(6) para tres), o corre el trabajo masivo en un solo worker. El límite se cuenta por clave, así que una segunda clave para un segundo worker trae sus propios 20 por segundo. A 20 solicitudes por segundo, los lotes de 100 mueven hasta 2,000 tweets por segundo y las páginas de búsqueda, 400.
#Guarda los tweets en JSONL o CSV
Escribe cada página según llegue, en lugar de mantener todos los tweets en memoria. JSON Lines, un objeto JSON por línea, conserva todos los campos y se puede ampliar sin riesgo; el CSV se abre en Excel y Google Sheets. Los dos flujos de abajo esperan a drain cuando el disco se atrasa.
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();
La marca de orden de bytes al inicio del CSV le indica a Excel que el archivo es UTF-8, así que los emoji y los acentos se ven bien. Cada celda va entre comillas, de modo que las comas, las comillas y los saltos de línea dentro de un tweet se quedan en su columna. Quinientas páginas dan hasta 10,000 tweets con 500 solicitudes; la memoria se mantiene estable sin importar cuánto dure la ejecución.
Para tener en cuenta
Los números de JavaScript guardan enteros con exactitud solo hasta 2^53, unos 9 mil billones, y los IDs de tweets tienen 19 dígitos.
Number("1985432176540981234")devuelve 1985432176540981200, que es otro tweet. Sorsa API envía cada ID y cada cursor como string, así que déjalos como strings, compáralos como strings y usaBigInt(id)cuando necesites ordenar por ID.
#Agrega tipos de TypeScript para las respuestas
Quien usa TypeScript obtiene seguridad de tipos con unas pocas interfaces que se llaman exactamente como los campos de la API. Las de abajo siguen la referencia OpenAPI vigente y cubren los campos que lee la mayoría del código; la referencia lista el resto.
// 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;
}
Los campos opcionales llevan un ?: un tweet citado o republicado existe solo en las citas y los reposts, así que el compilador te obliga a comprobarlo antes de leerlo. Haz la conversión en el borde, const page = (await sorsa(path, { body })) as TweetsPage, y el resto del código queda verificado. Node 22.18 y posteriores ejecutan archivos .ts directamente quitando los tipos, así que node --env-file=.env job.ts funciona sin paso de compilación. Para todos los endpoints a la vez, un generador de OpenAPI como openapi-typescript puede convertir la referencia completa en tipos.
#¿Se puede llamar a una API de Twitter desde JavaScript en el navegador?
No: una clave de API en el código del navegador la puede leer cualquiera que abra las herramientas de desarrollo, y Sorsa API está pensada para llamadas desde el servidor, así que los navegadores reciben errores de CORS. Pon una ruta pequeña en tu servidor, guarda la clave ahí y deja que el front end llame a tu ruta.
// 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);
El navegador ejecuta entonces fetch("/api/tweets?q=nodejs") contra tu propio dominio y nunca ve la clave. El mismo patrón sirve para cualquier entorno de servidor: un Cloudflare Worker o una función de Lambda guarda la clave en sus variables de entorno, y la página solo habla con ella. Guarda en caché la respuesta de la ruta durante un minuto cuando muchos visitantes piden lo mismo: Sorsa API devuelve datos frescos en cada solicitud, y una respuesta en caché sirve a todos por el costo de una solicitud.
#Cuánto cuesta un trabajo en Node.js
Sorsa API cobra por solicitud, sin importar lo que devuelva el endpoint: los resultados de búsqueda cuestan desde $0.07 por 1,000 tweets, los lotes desde $0.014, y las solicitudes fallidas nunca se cobran. La API oficial de X cobra en cambio por objeto: $5.00 por 1,000 posts, y a partir del 22 de noviembre de 2026 hasta $15.00 por 1,000 cuando el código expande el autor de cada post, como hacen la mayoría de las búsquedas con twitter-api-v2.
API oficial de X
$5,000
para 1M tweets
$0.005 por post
Sorsa API
desde$70
para 1M tweets
desde $0.07 por 1,000
Cada tweet incluye el perfil de su autor
El constructor de abajo escribe el bucle de Node.js para un trabajo y muestra sus solicitudes, el tiempo mínimo con una sola clave y el costo con la tarifa más baja de Sorsa API.
// sorsa() y pages() vienen de los helpers de arriba
const body = { query: "nodejs lang:en" };
for await (const page of pages("/search-tweets", body, 5000)) {
save(page.tweets); // hasta 20 tweets por página
}
- Solicitudes
- 5,000
- Lo más rápido con una sola clave
- unos 4 min
- Sorsa API
- desde $7
Tiempo a 20 solicitudes por segundo; costo a $1.40 por cada 1,000 solicitudes. Una búsqueda es una sola cadena de cursores, así que divídela en ventanas de fechas para correr carriles en paralelo.
El tiempo es el más rápido que permite una clave; una sola cadena de cursores avanza al ritmo de sus idas y vueltas, así que divide las tareas grandes en carriles.
La tarifa por 1,000 solicitudes va de $4.90 en Starter a $1.40 en Hyperscale, y todos los endpoints usan la misma tarifa. Consultar cuánto te queda no cuesta nada: GET /key-usage-info (se abre en una pestaña nueva) nunca descuenta de tus solicitudes.
import { sorsa } from "./sorsa.mjs";
const usage = await sorsa("/key-usage-info");
console.log(usage.remaining_requests, "left until", usage.valid_until);
#Cómo pasar el código de twitter-api-v2 a Sorsa API
La mayoría de las llamadas de twitter-api-v2 se corresponden con un endpoint de Sorsa API, y el código pierde sus expansiones y listas de campos, porque cada tweet ya trae su autor y sus conteos. La tabla empareja los métodos comunes de client.v2 con sus endpoints; para migrar desde la API de Twitter/X en todo un código base, esa guía mapea cada endpoint v2 y cada campo de respuesta.
| Método de twitter-api-v2 | Endpoint | Qué cambia |
|---|---|---|
search | POST /search-tweets | Cualquier fecha desde 2006, no solo los últimos 7 días |
searchAll | POST /search-tweets | El mismo endpoint con since: y until: en la consulta |
userTimeline | POST /user-tweets | Basta con un nombre de usuario, y no hay tope de 3,200 posts |
userByUsername | GET /info (se abre en una pestaña nueva) | Todos los campos del perfil, sin user.fields |
usersByUsernames | GET /info-batch | 100 nombres de usuario por solicitud |
tweets | POST /tweet-info-bulk | 100 IDs o enlaces por solicitud |
followers | GET /followers | 200 seguidores por página |
tweetRetweetedBy | POST /retweeters (se abre en una pestaña nueva) | El mismo bucle de cursor |
tweet | ninguno | Solo lectura: publica con la API oficial de X |
search- Endpoint
POST /search-tweets- Qué cambia
- Cualquier fecha desde 2006, no solo los últimos 7 días
searchAll- Endpoint
POST /search-tweets- Qué cambia
- El mismo endpoint con
since:yuntil:en la consulta
userTimeline- Endpoint
POST /user-tweets- Qué cambia
- Basta con un nombre de usuario, y no hay tope de 3,200 posts
userByUsername- Qué cambia
- Todos los campos del perfil, sin
user.fields
usersByUsernames- Endpoint
GET /info-batch- Qué cambia
- 100 nombres de usuario por solicitud
tweets- Endpoint
POST /tweet-info-bulk- Qué cambia
- 100 IDs o enlaces por solicitud
followers- Endpoint
GET /followers- Qué cambia
- 200 seguidores por página
tweetRetweetedBy- Qué cambia
- El mismo bucle de cursor
tweet- Endpoint
- ninguno
- Qué cambia
- Solo lectura: publica con la API oficial de X
Los nombres de los campos también cambian: text pasa a ser full_text, public_metrics.like_count pasa a ser likes_count, author_id con includes.users pasa a ser el objeto user, y meta.next_token pasa a ser next_cursor. El fetchNext() del paginador pasa a ser el generador pages de arriba, que se detiene en tu presupuesto de páginas y no en un límite de tasa. POST /user-tweets también llega más allá de los 3,200 posts más recientes, donde se detiene el timeline oficial (documentación de timelines de X (se abre en una pestaña nueva)), y la búsqueda reciente de la API oficial de X cubre solo los últimos 7 días (documentación de búsqueda de X (se abre en una pestaña nueva)).
#Haz hoy tu primera solicitud en Node.js
Leer datos de X desde Node.js necesita fetch, una clave y cuatro piezas pequeñas: sorsa para los reintentos, pages para los cursores, chunks para los lotes de 100 y un regulador para 20 solicitudes por segundo. Sorsa API es la API de Twitter con la que conviene empezar para esto: 20 tweets por solicitud de búsqueda desde $0.07 por 1,000, el autor dentro de cada tweet, búsqueda desde 2006 y 100 solicitudes gratis que no piden tarjeta y nunca vencen. Para llevar los mismos datos a un flujo de trabajo sin código, mira la API de Twitter en n8n.
#Preguntas frecuentes
¿Qué API de Twitter debería usar con Node.js?
Para leer datos de X desde Node.js, usa Sorsa API: una clave en un encabezado ApiKey con el fetch integrado, resultados de búsqueda desde $0.07 por 1,000 tweets y lotes desde $0.014, 20 solicitudes por segundo en todos los planes, búsqueda desde 2006 y ningún inicio de sesión de X, así que tu cuenta nunca corre riesgo. Para publicar también, combínala con la API oficial de X.
¿Hay un SDK oficial de la API de Twitter para Node.js?
Sí. X publica un SDK de TypeScript, @xdevplatform/xdk (versión 0.6.6), que funciona en Node.js y en empaquetadores. Es un cliente de la API oficial de X, así que cada llamada sigue necesitando una cuenta de desarrollador, un bearer token y créditos prepagados a $0.005 por post. Sorsa API no necesita ningún SDK: fetch simple cubre sus 45 endpoints.
¿Cuál es el mejor paquete de npm para datos de Twitter/X?
Para Sorsa API, ninguno: el fetch integrado de Node y las funciones de 40 líneas de esta guía cubren todos los endpoints, sin dependencias que actualizar. Para la API oficial de X, twitter-api-v2 es el cliente más instalado, con unas 336 mil descargas por semana y la versión 1.31.0, y junto a él está el @xdevplatform/xdk de la propia X; los dos facturan tus créditos de X por objeto.
¿Hay un paquete de npm gratuito para obtener tweets sin una clave de API?
Paquetes como rettiwt-api leen la app web de X en lugar de una API. Sin iniciar sesión solo alcanzan unas pocas consultas; para el resto envían las cookies de tu cuenta de X en cada solicitud. Eso vincula el tráfico a tu cuenta, y se rompen cuando X cambia su app web. Sorsa API nunca pide tu inicio de sesión de X, y sus 100 solicitudes gratis no piden tarjeta. Los pros y contras están en cómo hacer scraping de Twitter.
¿Puedo publicar tweets desde Node.js con Sorsa API?
No. Sorsa API es de solo lectura por diseño: lee posts, perfiles y seguidores públicos y nunca publica, da me gusta, sigue cuentas ni envía mensajes, así que una clave no puede actuar sobre ninguna cuenta de X. Para publicar desde Node.js, usa la API oficial de X con un token de usuario de OAuth, por ejemplo el client.v2.tweet de twitter-api-v2 a $0.015 por post, y deja Sorsa API para las lecturas.
¿Puedo obtener datos de Twitter en Node.js sin una cuenta de desarrollador?
Sí. Sorsa API solo necesita su propia clave: te registras, defines el encabezado ApiKey y llamas a cualquiera de sus endpoints desde Node.js, sin cuenta de desarrollador de X, sin revisión de app y sin OAuth. Las primeras 100 solicitudes son gratis y obtienen hasta 2,000 tweets por búsqueda o 10,000 en lotes de 100. Las opciones se comparan en API de Twitter sin cuenta de desarrollador.
#Cómo lo comprobamos
Escribimos cada solicitud a partir de la referencia OpenAPI vigente de Sorsa API (45 endpoints) y de su documentación sobre autenticación, límites de tasa y paginación, leída el 2 de noviembre de 2026. No teníamos una clave de Sorsa API para esta revisión, así que la respuesta usa valores de demostración. Ejecutamos los bloques de Node.js en Node 26 contra un mock local que responde con las formas documentadas: la primera solicitud, la función de reintentos (dos 503 y luego una ráfaga de 60 llamadas), el generador de cursores, los lotes de 100, la exportación a JSONL y CSV, la consulta de uso de la clave y el regulador. Con 19 inicios por segundo, el regulador corrió 31 cadenas, 93 solicitudes, sin un solo 429 en tres ejecuciones; con exactamente 20, una ejecución de tres envió 29 solicitudes a los reintentos. Los fragmentos de Express, Next.js y twitter-api-v2 no se ejecutaron. El mismo día leímos los precios de X, su aviso del 22 de noviembre sobre expansiones y su página de herramientas en docs.x.com, las fichas de npm de twitter-api-v2 y @xdevplatform/xdk, y la tabla de versiones de Node.js.
Fuentes
- X API pay-per-usage pricing (docs.x.com) (se abre en una pestaña nueva)
- X API PPU: expansions update (X Developers, October 23, 2026) (se abre en una pestaña nueva)
- Tools and libraries: the official TypeScript SDK (docs.x.com) (se abre en una pestaña nueva)
- Timelines (docs.x.com) (se abre en una pestaña nueva)
- Search Posts (docs.x.com) (se abre en una pestaña nueva)
- twitter-api-v2 on npm (se abre en una pestaña nueva)
- @xdevplatform/xdk on npm (se abre en una pestaña nueva)
- Node.js releases (se abre en una pestaña nueva)
- Sorsa API reference, OpenAPI
- Sorsa API rate limits (se abre en una pestaña nueva)
- Sorsa API pagination (se abre en una pestaña nueva)