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 v2 | TwittrAPI | |
|---|---|---|
| Base URL | https://api.x.com/2 | https://api.twittrapi.com |
| Auth | Authorization: Bearer <token> (app-only) or OAuth | X-API-KEY: <key> |
| Onboarding | Developer Console, Developer Agreement, app | Sign up at /start |
| Page size | max_results (search: max_results 10 to 100as of Oct 2026) | count, default 20; X decides the final number |
| Pagination in | pagination_token (search also next_token) | cursor |
| Pagination out | meta.next_token | Bottom cursor inside X's JSON (cursorType: "Bottom") |
| Field selection | post.fields, user.fields, expansions | None; the full object comes back |
| Response | data, includes, meta, errors | X's own web GraphQL JSON |
| Rate limits | Per endpoint per 15-minute window | One per-second limit per key for all endpoints |
| Billing | Per resource read; the same resource is billed once per 24-hour UTC dayas of Oct 2026 | Per call |
| Search window | Recent search: last 7 daysas of Oct 2026; full archive on /search/all | What X's search tab returns |
Endpoint mapping
| X API v2 | TwittrAPI | Notes |
|---|---|---|
GET /2/tweets/{id} | GET /simpletweet/{tweetId} | |
GET /2/tweets?ids= | GET /simpletweet/{tweetId} per ID | No 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/recent | GET /search?searchItem=&category=Latest | query also works as the name. Same operator syntax as X search. |
GET /2/tweets/search/all | GET /search with since: / until: operators | Results 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}/tweets | GET /user/{userId}/tweets | With replies: GET /user/{userId}/tweetsAndReplies. No exclude; filter retweets yourself. |
GET /2/users/{id}/followers | GET /user/{userId}/followers | |
GET /2/users/{id}/following | GET /user/{userId}/followings | /following is an alias. |
GET /2/tweets/{id}/quote_tweets | GET /tweet/{tweetId}/quotes | |
GET /2/tweets/{id}/retweeted_by | GET /tweet/{tweetId}/retweets | |
GET /2/lists/{id}/tweets | GET /lists/tweets?listId= | |
GET /2/lists/{id}/members | GET /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/search | GET /communities/search/{searchTerm} | |
GET /2/spaces/{id}, /2/spaces/search | not available | |
| Post counts | not available | |
| Filtered stream (following accounts) | Account monitoring: new tweets by webhook within 1, 5 or 60 s | Keyword rules: poll search with since_id: instead. |
| Posting for users, likes, DMs, user-context endpoints | keep 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 v2 | X's GraphQL JSON (TwittrAPI) |
|---|---|
data.id | rest_id |
data.text | legacy.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_count | legacy.favorite_count |
public_metrics.repost_count / retweet_count | legacy.retweet_count |
public_metrics.reply_count / quote_count | legacy.reply_count / legacy.quote_count |
public_metrics.impression_count | views.count (a string) |
author_id + includes.users[].username | core.user_results.result.core.screen_name (older responses: .legacy.screen_name) |
conversation_id | legacy.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
pagination_tokenbecomescursor, andmeta.next_tokenbecomes 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
countand check what comes back, since every page is a billed call. since_id/until_idandstart_time/end_timebecome 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/expansionshandling 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_tokenloops with cursor pagination or the SDK paginator. - Parse
legacy.created_atif 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.
