# 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.

HTML page: https://www.twittrapi.com/guides/migrate-from-official-x-api
Competitor facts checked on 2026-10-10.

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](https://www.twittrapi.com/docs/reference); every TwittrAPI path below links to its reference page. For prices and trade-offs, read the [comparison](https://www.twittrapi.com/compare/official-x-api) 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](https://www.twittrapi.com/start) |
| Page size | `max_results` (search: max_results 10 to 100 (as of October 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 day (as of October 2026) | Per call |
| Search window | Recent search: last 7 days (as of October 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}`](https://www.twittrapi.com/docs/reference/simpletweet-tweetid) | |
| `GET /2/tweets?ids=` | [`GET /simpletweet/{tweetId}`](https://www.twittrapi.com/docs/reference/simpletweet-tweetid) per ID | No multi-post lookup. |
| `GET /2/tweets/search/recent?query=conversation_id:{id}` | [`GET /tweet/{tweetId}`](https://www.twittrapi.com/docs/reference/tweet-tweetid) | Post plus replies, `rankingMode` Relevance, Recency or Likes. |
| `GET /2/tweets/search/recent` | [`GET /search?searchItem=&category=Latest`](https://www.twittrapi.com/docs/reference/search) | `query` also works as the name. Same operator syntax as X search. |
| `GET /2/tweets/search/all` | [`GET /search`](https://www.twittrapi.com/docs/reference/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}`](https://www.twittrapi.com/docs/reference/user-by-username-username) | |
| `GET /2/users/{id}` | [`GET /user/{userId}`](https://www.twittrapi.com/docs/reference/user-userid) | |
| `GET /2/users?ids=` | [`GET /user/users/by/ids?ids=`](https://www.twittrapi.com/docs/reference/user-users-by-ids) | Up to 100 IDs per call. |
| `GET /2/users/{id}/tweets` | [`GET /user/{userId}/tweets`](https://www.twittrapi.com/docs/reference/user-userid-tweets) | With replies: [`GET /user/{userId}/tweetsAndReplies`](https://www.twittrapi.com/docs/reference/user-userid-tweetsandreplies). No `exclude`; filter retweets yourself. |
| `GET /2/users/{id}/followers` | [`GET /user/{userId}/followers`](https://www.twittrapi.com/docs/reference/user-userid-followers) | |
| `GET /2/users/{id}/following` | [`GET /user/{userId}/followings`](https://www.twittrapi.com/docs/reference/user-userid-followings) | `/following` is an alias. |
| `GET /2/tweets/{id}/quote_tweets` | [`GET /tweet/{tweetId}/quotes`](https://www.twittrapi.com/docs/reference/tweet-tweetid-quotes) | |
| `GET /2/tweets/{id}/retweeted_by` | [`GET /tweet/{tweetId}/retweets`](https://www.twittrapi.com/docs/reference/tweet-tweetid-retweets) | |
| `GET /2/lists/{id}/tweets` | [`GET /lists/tweets?listId=`](https://www.twittrapi.com/docs/reference/lists-tweets) | |
| `GET /2/lists/{id}/members` | [`GET /lists/members?listId=`](https://www.twittrapi.com/docs/reference/lists-members) | |
| `GET /2/trends/by/woeid/{woeid}` | [`GET /trends/{locationId}`](https://www.twittrapi.com/docs/reference/trends-locationid) | `locationId` is the WOEID. |
| `GET /2/communities/{id}` | [`GET /communities/{communityId}`](https://www.twittrapi.com/docs/reference/communities-communityid) | Also tweets, members, moderators. |
| `GET /2/communities/search` | [`GET /communities/search/{searchTerm}`](https://www.twittrapi.com/docs/reference/communities-search-searchterm) | |
| `GET /2/spaces/{id}`, `/2/spaces/search` | not available | |
| Post counts | not available | |
| Filtered stream (following accounts) | [Account monitoring](https://www.twittrapi.com/twitter-streaming-api): 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](https://www.twittrapi.com/docs/response-format) show the structure, and our [examples repository](https://github.com/twittrapi/examples) has extractors for Python, Node.js and Go.

## Before and after: Python

Before, with the X API:

```python
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](https://pypi.org/project/twittrapi/) and the extractor from the examples repository:

```python
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:

```js
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](https://www.npmjs.com/package/twittrapi):

```js
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

Figure: paging through followers with a cursor. Call /user/{userId}/followers, pass the returned cursor back as `cursor` for the next page, and stop at the last page. Every page is one call ($0.065 to $0.09 per 1,000 calls).

- `pagination_token` becomes `cursor`, and `meta.next_token` becomes the bottom cursor in the response. The SDKs find it for you; see [Pagination](https://www.twittrapi.com/docs/helpful-infos/cursor).
- There is no `result_count`. Count the items you extract.
- v2 lets you ask for up to 1,000 followers per request (as of October 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](https://www.twittrapi.com/docs/helpful-infos/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](https://www.twittrapi.com/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](https://www.twittrapi.com/docs/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}`](https://www.twittrapi.com/docs/reference/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

- [X API: search recent posts](https://docs.x.com/x-api/posts/search-recent-posts)
- [X API pricing (docs.x.com)](https://docs.x.com/x-api/getting-started/pricing)
- [X API: followers](https://docs.x.com/x-api/users/get-followers)
- [X API: get post by ID](https://docs.x.com/x-api/posts/get-post-by-id)
- [X API: get user by username](https://docs.x.com/x-api/users/get-user-by-username)
- [X API: user posts](https://docs.x.com/x-api/users/get-posts)
- [X API rate limits (docs.x.com)](https://docs.x.com/x-api/fundamentals/rate-limits)
