# Migrate from twitterapi.io to TwittrAPI

> Move a twitterapi.io integration to TwittrAPI: endpoint mapping, auth header, cursor pagination, response shape and before/after code in Python and Node.js.

HTML page: https://www.twittrapi.com/guides/migrate-from-twitterapi-io
Competitor facts checked on 2026-10-10.

This guide moves a twitterapi.io read integration to TwittrAPI. twitterapi.io paths and parameters are taken from its public docs 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. Before you start, read the [comparison](https://www.twittrapi.com/compare/twitterapi-io) to make sure the switch makes sense for your workload.

## What changes

| | twitterapi.io | TwittrAPI |
| --- | --- | --- |
| Base URL | `https://api.twitterapi.io` | `https://api.twittrapi.com` |
| Auth header | `X-API-Key: <key>` | `X-API-KEY: <key>` (header names are case-insensitive) |
| Paths | `/twitter/...` with query parameters | Resource paths, IDs in the path |
| Pagination in | `cursor` (empty string for page one) | `cursor` (leave it out for page one) |
| Pagination out | `next_cursor`, `has_next_page` | Bottom cursor inside X's JSON (`cursorType: "Bottom"`) |
| Page size | Fixed per endpoint (up to 20 tweets per page (as of October 2026); followers pageSize up to 200 (as of October 2026)) | `count`, default 20; X decides the final number |
| Response | Flat camelCase objects, author inside the tweet | X's own JSON, passed through |
| Envelope | `status` and `message` / `msg` | No envelope on success; errors are `{"error": "..."}` with a non-2xx status |
| Billing | Credits per item, minimum per call | One call per request |

## Endpoint mapping

| twitterapi.io | TwittrAPI | Notes |
| --- | --- | --- |
| `GET /twitter/tweets?tweet_ids=1,2` | [`GET /simpletweet/{tweetId}`](https://www.twittrapi.com/docs/reference/simpletweet-tweetid) | One call per tweet. No multi-tweet lookup. |
| `GET /twitter/tweet/replies?tweetId=` | [`GET /tweet/{tweetId}`](https://www.twittrapi.com/docs/reference/tweet-tweetid) | Returns the tweet and its replies. `rankingMode`: Relevance, Recency or Likes. About 20 replies per page. |
| `GET /twitter/user/info?userName=` | [`GET /user/by/username/{username}`](https://www.twittrapi.com/docs/reference/user-by-username-username) | |
| `GET /twitter/user/batch_info_by_ids?userIds=` | [`GET /user/users/by/ids?ids=`](https://www.twittrapi.com/docs/reference/user-users-by-ids) | Up to 100 IDs per call. |
| `GET /twitter/user/last_tweets?userId=` or `userName=` | [`GET /user/{userId}/tweets`](https://www.twittrapi.com/docs/reference/user-userid-tweets) | Handle only: `/user/-1/tweets?username=jack`. |
| `GET /twitter/user/last_tweets?includeReplies=true` | [`GET /user/{userId}/tweetsAndReplies`](https://www.twittrapi.com/docs/reference/user-userid-tweetsandreplies) | |
| `GET /twitter/tweet/advanced_search?query=&queryType=Latest` | [`GET /search?searchItem=&category=Latest`](https://www.twittrapi.com/docs/reference/search) | `query` also works as the parameter name. Categories: Top, Latest, People, Photos, Videos. Same X search operators. |
| `GET /twitter/user/followers?userName=` | [`GET /user/{userId}/followers`](https://www.twittrapi.com/docs/reference/user-userid-followers) | Use `-1` and `?username=` for a handle. |
| `GET /twitter/user/followings?userName=` | [`GET /user/{userId}/followings`](https://www.twittrapi.com/docs/reference/user-userid-followings) | `/following` is an alias. |
| Get User Verified Followers | [`GET /user/{userId}/verifiedFollowers`](https://www.twittrapi.com/docs/reference/user-userid-verifiedfollowers) | |
| Get User Mentions | [`GET /search?searchItem=@jack&category=Latest`](https://www.twittrapi.com/docs/reference/search) | No dedicated endpoint; use search. |
| Check Follow Relationship | [`GET /user/friendships/show?source_screen_name=&target_screen_name=`](https://www.twittrapi.com/docs/reference/user-friendships-show) | |
| Get User Profile About | [`GET /user/about/{username}`](https://www.twittrapi.com/docs/reference/user-about-username) | |
| `GET /twitter/tweet/quotes?tweetId=` | [`GET /tweet/{tweetId}/quotes`](https://www.twittrapi.com/docs/reference/tweet-tweetid-quotes) | |
| `GET /twitter/tweet/retweeters?tweetId=` | [`GET /tweet/{tweetId}/retweets`](https://www.twittrapi.com/docs/reference/tweet-tweetid-retweets) | |
| Get Article | [`GET /tweet/{tweetId}/article`](https://www.twittrapi.com/docs/reference/tweet-tweetid-article) | |
| `GET /twitter/list/tweets_timeline?listId=` | [`GET /lists/tweets?listId=`](https://www.twittrapi.com/docs/reference/lists-tweets) | |
| `GET /twitter/list/members?list_id=` | [`GET /lists/members?listId=`](https://www.twittrapi.com/docs/reference/lists-members) | |
| Get List Followers | [`GET /lists/followers?listId=`](https://www.twittrapi.com/docs/reference/lists-followers) | |
| `GET /twitter/community/info?community_id=` | [`GET /communities/{communityId}`](https://www.twittrapi.com/docs/reference/communities-communityid) | |
| `GET /twitter/community/tweets?community_id=` | [`GET /communities/{communityId}/tweets`](https://www.twittrapi.com/docs/reference/communities-communityid-tweets) | |
| Get Community Members | [`GET /communities/{communityId}/members`](https://www.twittrapi.com/docs/reference/communities-communityid-members) | |
| Get Community Moderators | [`GET /communities/{communityId}/moderators`](https://www.twittrapi.com/docs/reference/communities-communityid-moderators) | |
| `GET /twitter/trends?woeid=` | [`GET /trends/{locationId}`](https://www.twittrapi.com/docs/reference/trends-locationid) | `locationId` is the WOEID. [`GET /misc/locations`](https://www.twittrapi.com/docs/reference/misc-locations) lists them. |
| `GET /twitter/spaces/detail` | not available | |
| Get User Followers IDs (Bulk) | not available | |
| User monitoring (`add_user_to_monitor_tweet`, stream) | [Account monitoring](https://www.twittrapi.com/docs/monitoring) (`POST /monitors/accounts`, webhook or `GET /monitors/events`) | No follower threshold: every public account is checked at the plan's guaranteed interval. |
| Webhook / websocket filter rules on search queries | not available | Poll search instead. |
| Write actions (create tweet, like, follow, ...) | `/client/*` [account actions](https://www.twittrapi.com/docs/account-actions) | Need your X session cookie and your own proxy. |

Rows that show a twitterapi.io page title instead of a path are endpoints whose exact path we did not copy from its docs; look them up there.

## Before and after: Python

Before, with `requests` against twitterapi.io:

```python
import os, requests

r = requests.get(
    "https://api.twitterapi.io/twitter/tweet/advanced_search",
    headers={"X-API-Key": os.environ["TWITTERAPI_IO_KEY"]},
    params={"query": "from:jack lang:en", "queryType": "Latest", "cursor": ""},
)
data = r.json()
for t in data["tweets"]:
    print(t["id"], t["author"]["userName"], t["likeCount"], t["text"])
next_cursor = data["next_cursor"] if data["has_next_page"] else None
```

After, with the [Python SDK](https://pypi.org/project/twittrapi/) (`pip install twittrapi`). `tweets()` is the extractor from our [examples repository](https://github.com/twittrapi/examples); it flattens X's JSON into simple dicts:

```python
from twittrapi import TwittrAPI, extract_bottom_cursor
from extract import tweets  # python/_shared/extract.py from the examples repo

client = TwittrAPI()  # reads TWITTRAPI_API_KEY

page = client.search.tweets("from:jack lang:en", category="Latest", count=20)
for t in tweets(page):
    print(t["id"], t["author"], t["likes"], t["text"])
next_cursor = extract_bottom_cursor(page)  # None at the end
```

To walk several pages, let the SDK follow the cursor. Every page is one billed call:

```python
for page in client.paginate(client.search.tweets, "from:jack lang:en", category="Latest", max_pages=5):
    for t in tweets(page):
        ...
```

## Before and after: Node.js

Before:

```js
const res = await fetch(
  "https://api.twitterapi.io/twitter/user/last_tweets?userName=jack&cursor=",
  { headers: { "X-API-Key": process.env.TWITTERAPI_IO_KEY } },
);
const data = await res.json();
for (const t of data.tweets) console.log(t.id, t.likeCount, t.text);
```

After, with the [Node SDK](https://www.npmjs.com/package/twittrapi) (`npm install twittrapi`):

```js
import { TwittrAPI, extractBottomCursor } from "twittrapi";
import { extractTweets } from "./extract.mjs"; // node/_shared/extract.mjs from the examples repo

const client = new TwittrAPI(); // reads TWITTRAPI_API_KEY

const page = await client.users.tweets({ username: "jack", count: 20 });
for (const t of extractTweets(page)) console.log(t.id, t.likes, t.text);
const cursor = extractBottomCursor(page); // undefined at the end
```

## Keep your data model: an adapter

If the rest of your code expects twitterapi.io's tweet objects, map ours to that shape in one place:

```python
def to_twitterapi_io_shape(t: dict) -> dict:
    """t is one item from tweets(page)."""
    return {
        "id": t["id"],
        "url": t["url"],
        "text": t["text"],
        "createdAt": t["created_at"],
        "likeCount": t["likes"],
        "retweetCount": t["retweets"],
        "replyCount": t["replies"],
        "viewCount": t["views"],
        "author": {"userName": t["author"]},
    }
```

Fields twitterapi.io adds that the extractor does not (for example `bookmarkCount` or `quoteCount`) are in X's JSON under the tweet's `legacy` object; extend the extractor if you use them. The [response format docs](https://www.twittrapi.com/docs/response-format) explain where things are.

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

- Leave `cursor` out on the first page instead of sending an empty string.
- There is no `has_next_page`. Stop when there is no bottom cursor, when it repeats, or when a page has no items. See [Pagination](https://www.twittrapi.com/docs/helpful-infos/cursor).
- Page sizes differ. A twitterapi.io follower page can hold up to 200 accounts; on our side set `count` and check what comes back.
- Cursors are not interchangeable. Start fresh on our side; do not reuse stored twitterapi.io cursors.

## Errors and billing

Errors are `{"error": "..."}` with an HTTP status. 401 means a missing or unknown key, 402 no balance, 429 over your per-second limit (not charged). Calls that end in 400, 408, 409 or 5xx are refunded, and the `X-Refunded` header says so. Every billed response has `X-Price-Per-Call` and `X-Remaining-Balance` headers, which helps when you compare costs during the switch. Details: [Errors](https://www.twittrapi.com/docs/helpful-infos/error-handling) and [Pricing and billing](https://www.twittrapi.com/docs/billing).

## Checklist

- List every twitterapi.io endpoint you call and how often.
- Check each against the mapping table; plan around Spaces, follower ID lists and webhooks if you use them.
- Create an account at [/start](https://www.twittrapi.com/start) and set `TWITTRAPI_API_KEY`.
- Install the SDK (`pip install twittrapi` or `npm install twittrapi`) or switch the base URL and header.
- Replace `userName` lookups on timelines with user IDs, or use `-1` plus `?username=`.
- Write the adapter from X's JSON to your data model, and test it on saved responses.
- Replace `has_next_page` checks with bottom-cursor checks; do not reuse old cursors.
- Map error handling to our status codes; back off on 429.
- Run both services side by side on a sample, compare items and cost.
- Move the rest of the traffic.

Reading data needs no X account; the optional [account actions](https://www.twittrapi.com/docs/account-actions) use your own X session cookie and proxy. TwittrAPI is not affiliated with X Corp. or twitterapi.io.

## Frequently asked questions

### How long does a migration from twitterapi.io take?

The HTTP calls change quickly. Most of the work is the response parser, because twitterapi.io returns flat camelCase objects and TwittrAPI passes X's own JSON through. With an adapter that maps our JSON to the objects your code already uses, the rest of your code does not change.

### Do I need a username or a user ID?

Our user timelines take a numeric user ID in the path. If you only have a handle, pass -1 as the ID and add ?username=. It is still one call, just a little slower.

### Is there an equivalent of has_next_page?

No. Look for the bottom cursor in the response. If there is none, or it repeats, or the page holds no items, you are at the end. The SDK paginator does those checks for you.

### Can I run both services during the switch?

Yes. Put both behind the same adapter interface and route a share of traffic to each until the results match.


## Sources

- [twitterapi.io: advanced search](https://docs.twitterapi.io/api-reference/endpoint/tweet_advanced_search)
- [twitterapi.io: followers](https://docs.twitterapi.io/api-reference/endpoint/get_user_followers)
- [twitterapi.io docs index](https://docs.twitterapi.io/llms.txt)
- [twitterapi.io: get tweets by IDs](https://docs.twitterapi.io/api-reference/endpoint/get_tweet_by_ids)
- [twitterapi.io: user last tweets](https://docs.twitterapi.io/api-reference/endpoint/get_user_last_tweets)
- [twitterapi.io: tweet replies](https://docs.twitterapi.io/api-reference/endpoint/get_tweet_reply)
