Migrate read calls from the official X API to TwittrAPI

Move data reads from X API v2 to TwittrAPI: endpoint mapping, Bearer token to API key, pagination_token to cursor, data/includes to X's GraphQL JSON, with Python and Node.js code.

TwittrAPIChecked Competitor facts as of October 2026

Every number links to its source on the provider's own pages.

Migrate from the X API endpoint by endpoint — Endpoint mapping from X API v2 to TwittrAPI: /2/users/by/username/{username} becomes /user/by/username/{username}; /2/users/{id}/tweets becomes /user/{userId}/tweets; /2/tweets/search/recent becomes /search?category=Latest; /2/users/{id}/followers becomes /user/{userId}/followers.
On this page
  1. What changes
  2. Endpoint mapping
  3. Response shape
  4. Before and after: Python
  5. Before and after: Node.js
  6. Pagination differences
  7. Billing and limits
  8. Checklist
  9. Frequently asked questions

This guide moves data reads from X API v2 to TwittrAPI. It does not cover posting on behalf of users or user-context OAuth, which belong on the official API. X API paths and parameters come from docs.x.com as of October 2026. Ours are checked against our endpoint reference; every TwittrAPI path below links to its reference page. For prices and trade-offs, read the comparison first.

What changes

Official X API v2TwittrAPI
Base URLhttps://api.x.com/2https://api.twittrapi.com
AuthAuthorization: Bearer <token> (app-only) or OAuthX-API-KEY: <key>
OnboardingDeveloper Console, Developer Agreement, appSign up at /start
Page sizemax_results (search: max_results 10 to 100as of Oct 2026)count, default 20; X decides the final number
Pagination inpagination_token (search also next_token)cursor
Pagination outmeta.next_tokenBottom cursor inside X's JSON (cursorType: "Bottom")
Field selectionpost.fields, user.fields, expansionsNone; the full object comes back
Responsedata, includes, meta, errorsX's own web GraphQL JSON
Rate limitsPer endpoint per 15-minute windowOne per-second limit per key for all endpoints
BillingPer resource read; the same resource is billed once per 24-hour UTC dayas of Oct 2026Per call
Search windowRecent search: last 7 daysas of Oct 2026; full archive on /search/allWhat X's search tab returns

Endpoint mapping

X API v2TwittrAPINotes
GET /2/tweets/{id}GET /simpletweet/{tweetId}
GET /2/tweets?ids=GET /simpletweet/{tweetId} per IDNo multi-post lookup.
GET /2/tweets/search/recent?query=conversation_id:{id}GET /tweet/{tweetId}Post plus replies, rankingMode Relevance, Recency or Likes.
GET /2/tweets/search/recentGET /search?searchItem=&category=Latestquery also works as the name. Same operator syntax as X search.
GET /2/tweets/search/allGET /search with since: / until: operatorsResults are what X's search returns, not a guaranteed full archive.
GET /2/users/by/username/{username}GET /user/by/username/{username}
GET /2/users/{id}GET /user/{userId}
GET /2/users?ids=GET /user/users/by/ids?ids=Up to 100 IDs per call.
GET /2/users/{id}/tweetsGET /user/{userId}/tweetsWith replies: GET /user/{userId}/tweetsAndReplies. No exclude; filter retweets yourself.
GET /2/users/{id}/followersGET /user/{userId}/followers
GET /2/users/{id}/followingGET /user/{userId}/followings/following is an alias.
GET /2/tweets/{id}/quote_tweetsGET /tweet/{tweetId}/quotes
GET /2/tweets/{id}/retweeted_byGET /tweet/{tweetId}/retweets
GET /2/lists/{id}/tweetsGET /lists/tweets?listId=
GET /2/lists/{id}/membersGET /lists/members?listId=
GET /2/trends/by/woeid/{woeid}GET /trends/{locationId}locationId is the WOEID.
GET /2/communities/{id}GET /communities/{communityId}Also tweets, members, moderators.
GET /2/communities/searchGET /communities/search/{searchTerm}
GET /2/spaces/{id}, /2/spaces/searchnot available
Post countsnot available
Filtered stream (following accounts)Account monitoring: new tweets by webhook within 1, 5 or 60 sKeyword rules: poll search with since_id: instead.
Posting for users, likes, DMs, user-context endpointskeep on the official API

Response shape

A v2 post is data with public_metrics, and the author sits in includes.users, joined by author_id. In TwittrAPI's response the author is inside the post, and the counts use X's older names:

X API v2X's GraphQL JSON (TwittrAPI)
data.idrest_id
data.textlegacy.full_text (long posts: note_tweet.note_tweet_results.result.text)
data.created_at (ISO 8601)legacy.created_at (Wed Oct 10 20:19:24 +0000 2018)
public_metrics.like_countlegacy.favorite_count
public_metrics.repost_count / retweet_countlegacy.retweet_count
public_metrics.reply_count / quote_countlegacy.reply_count / legacy.quote_count
public_metrics.impression_countviews.count (a string)
author_id + includes.users[].usernamecore.user_results.result.core.screen_name (older responses: .legacy.screen_name)
conversation_idlegacy.conversation_id_str

Timelines wrap posts in instructions and entries. The response format docs show the structure, and our examples repository has extractors for Python, Node.js and Go.

Before and after: Python

Before, with the X API:

import os, requests
 
r = requests.get(
    "https://api.x.com/2/tweets/search/recent",
    headers={"Authorization": f"Bearer {os.environ['X_BEARER_TOKEN']}"},
    params={
        "query": "from:jack -is:retweet",
        "max_results": 100,
        "post.fields": "created_at,public_metrics",  # older docs and clients call it tweet.fields
        "expansions": "author_id",
    },
)
body = r.json()
users = {u["id"]: u["username"] for u in body.get("includes", {}).get("users", [])}
for p in body.get("data", []):
    print(p["id"], users.get(p["author_id"]), p["public_metrics"]["like_count"], p["text"])
next_token = body.get("meta", {}).get("next_token")

After, with the Python SDK and the extractor from the examples repository:

from twittrapi import TwittrAPI
from extract import tweets  # python/_shared/extract.py from the examples repo
 
client = TwittrAPI()  # reads TWITTRAPI_API_KEY
 
for page in client.paginate(client.search.tweets, "from:jack -filter:retweets", category="Latest", max_pages=5):
    for t in tweets(page):
        print(t["id"], t["author"], t["likes"], t["text"])

Note the operator change: v2's -is:retweet is -filter:retweets (or -filter:nativeretweets) in X's search box syntax.

Before and after: Node.js

Before:

const res = await fetch("https://api.x.com/2/users/by/username/jack", {
  headers: { Authorization: `Bearer ${process.env.X_BEARER_TOKEN}` },
});
const { data } = await res.json();
const followers = await fetch(`https://api.x.com/2/users/${data.id}/followers?max_results=1000`, {
  headers: { Authorization: `Bearer ${process.env.X_BEARER_TOKEN}` },
}).then((r) => r.json());

After, with the Node SDK:

import { TwittrAPI } from "twittrapi";
import { extractProfile, extractUsers } from "./extract.mjs"; // node/_shared/extract.mjs from the examples repo
 
const client = new TwittrAPI(); // reads TWITTRAPI_API_KEY
 
const profile = extractProfile(await client.users.getByUsername("jack"));
for await (const page of client.paginate(client.users.followers, { userId: profile.id, count: 100 }, { maxPages: 5 })) {
  for (const u of extractUsers(page.data)) console.log(u.username, u.followers);
}

Pagination differences

Paging through followers with a cursor

Call 1

GET /user/{userId}/followers

→ ~50–70 followers

bottom cursor: "A1"

Call 2

?cursor=A1

→ ~50–70 followers

bottom cursor: "B2"

Call n

?cursor=…

→ last followers

no bottom cursor

Every page is one call, billed at $0.065 to $0.09 per 1,000 calls. X decides how many followers a page holds (about 50–70), so stop when there is no bottom cursor, it repeats or a page is empty instead of counting.
  • pagination_token becomes cursor, and meta.next_token becomes the bottom cursor in the response. The SDKs find it for you; see Pagination.
  • There is no result_count. Count the items you extract.
  • v2 lets you ask for up to 1,000 followers per requestas of Oct 2026. Our page size follows X's web app; set count and check what comes back, since every page is a billed call.
  • since_id / until_id and start_time / end_time become search operators: since_id:, max_id:, since:, until:, since_time:, until_time:.

Billing and limits

  • One request is one call, whatever it returns. There is no 24-hour deduplication, so cache what you re-read often.
  • The rate limit is per second for all endpoints together (30 requests per second after the first top-up). 429 is not charged. See Rate limits.
  • Calls that end in 400, 408, 409 or 5xx are refunded (X-Refunded: true).
  • There is no monthly read cap.

Checklist

  • Split your X API usage into reads and everything else (posting for users, OAuth, DMs). Move only the reads.
  • Map each read endpoint with the table above; plan around Spaces, counts and streaming if you use them.
  • Create an account at /start and set TWITTRAPI_API_KEY.
  • Replace post.fields / expansions handling with an extractor for X's GraphQL JSON.
  • Convert v2-only search operators (is:retweet, has:media) to X search box operators (filter:retweets, filter:media).
  • Replace pagination_token loops with cursor pagination or the SDK paginator.
  • Parse legacy.created_at if you store ISO dates.
  • Run both on a sample of your queries and compare results and cost.
  • Switch the reads; keep the official API for the rest.

TwittrAPI is a third-party service and is not affiliated with X Corp. Reading data needs no X account; optional account actions use your own X session cookie and proxy. Make sure an unofficial API fits your compliance requirements before you move production traffic.

Frequently asked questions

Should I move everything off the official X API?

No. Keep the official API for posting on behalf of users, OAuth flows, full-archive search and anything that needs an agreement with X. Move the reads where the price per post is the problem.

What replaces post.fields and expansions?

Nothing. TwittrAPI returns everything X's web app receives for the request, including the author inside each tweet. You pick the fields when you parse.

How do I get replies to a post?

On the X API you search for conversation_id: followed by the post ID. On TwittrAPI, GET /tweet/{tweetId} returns the post and its replies, about 20 per page, with a cursor for more. Search with conversation_id: also works.

Are user and post IDs the same?

Yes. Both use X's numeric IDs, so stored IDs keep working.

Sources (checked 2026-10-10T00:00:00.000Z)

Prices and limits change. If something here is out of date, write to support@twittrapi.com and we will correct it. Names of other services are used only to identify them; we are not affiliated with any of them or with X Corp.

Make your first call in a minute.

No X developer account. Prepaid, no subscription, and the balance does not expire.

$0.065per 1,000 calls