Migrate from SocialData to TwittrAPI

Move a SocialData integration to TwittrAPI: endpoint mapping, auth header, cursor pagination, the legacy fields you already know, and before/after code in Python and Node.js.

TwittrAPIChecked Competitor facts as of October 2026

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

Migrate from SocialData endpoint by endpoint — Endpoint mapping from SocialData to TwittrAPI: /twitter/user/{username} becomes /user/by/username/{username}; /twitter/user/{user_id}/tweets becomes /user/{userId}/tweets; /twitter/search?type=Latest becomes /search?category=Latest; POST /twitter/users-by-ids becomes /user/users/by/ids?ids=.
On this page
  1. What changes
  2. Endpoint mapping
  3. Response shape: same names, different nesting
  4. Before and after: Python
  5. Before and after: Node.js
  6. Keep your data model: an adapter
  7. Pagination differences
  8. Billing differences to plan for
  9. Checklist
  10. Frequently asked questions

This guide moves a SocialData read integration to TwittrAPI. SocialData paths and parameters come from its public docs as of October 2026. Ours are checked against our endpoint reference; every TwittrAPI path below links to its reference page. If you have not decided yet, the comparison covers price, limits and what each side does better.

What changes

SocialDataTwittrAPI
Base URLhttps://api.socialdata.toolshttps://api.twittrapi.com
Auth headerAuthorization: Bearer <key>X-API-KEY: <key>
Pagination incursorcursor
Pagination outnext_cursorBottom cursor inside X's JSON (cursorType: "Bottom")
Page sizeabout 20 tweets per pageas of Oct 2026, fixedcount, default 20; X decides the final number
ResponseFlat snake_case objectsX's own JSON; tweet fields sit in legacy
Errorsstatus and message; 402, 422, 500{"error": "..."} with a non-2xx status
Rate limit120 requests per minute per useras of Oct 202630 requests per second per key after the first top-up
BillingPer tweet or profile returnedOne call per request

Endpoint mapping

SocialDataTwittrAPINotes
GET /twitter/tweets/{id}GET /simpletweet/{tweetId}
GET /twitter/tweets/{id}/commentsGET /tweet/{tweetId}Tweet plus replies. rankingMode: Relevance, Recency or Likes. About 20 replies per page.
GET /twitter/tweets/{id}/quotesGET /tweet/{tweetId}/quotes
GET /twitter/tweets/{tweet_id}/retweeted_byGET /tweet/{tweetId}/retweets
GET /twitter/user/{username}GET /user/by/username/{username}By ID: GET /user/{userId}.
POST /twitter/users-by-idsGET /user/users/by/ids?ids=Up to 100 IDs, one call.
GET /twitter/user/{user_id}/tweetsGET /user/{userId}/tweets
GET /twitter/user/{user_id}/tweets-and-repliesGET /user/{userId}/tweetsAndReplies
GET /twitter/search?query=&type=LatestGET /search?searchItem=&category=Latestquery also works as the parameter name. Categories: Top, Latest, People, Photos, Videos.
GET /twitter/user/{user_id}/followersGET /user/{userId}/followers
GET /twitter/user/{user_id}/followingGET /user/{userId}/followings/following is an alias.
Verified followersGET /user/{userId}/verifiedFollowers
GET /twitter/list/{list_id}/tweetsGET /lists/tweets?listId=
GET /twitter/list/{list_id}/membersGET /lists/members?listId=
GET /twitter/community/{community_id}/tweetsGET /communities/{communityId}/tweets
Community detailsGET /communities/{communityId}
Community membersGET /communities/{communityId}/members
Community searchGET /communities/search/{searchTerm}
MentionsGET /search?searchItem=@jack&category=LatestNo dedicated endpoint; use search.
GET /twitter/space/{space_id}not available
Thread, highlights, affiliates, similar profiles, extended bionot available
Verify followingGET /user/friendships/show
Verify retweeted / commentednot availableScan GET /tweet/{tweetId}/retweets or the replies yourself.
Monitoring and webhooksnot availablePoll search or timelines.
(none)GET /trends/{locationId}, GET /misc/locationsTrends by WOEID.

Rows without a path on the SocialData side are endpoints whose exact path we did not copy from its docs.

Response shape: same names, different nesting

SocialData flattens a tweet into one object with the author in user. TwittrAPI passes X's JSON through, where a tweet looks roughly like this:

{
  "rest_id": "1790000000000000000",
  "core": { "user_results": { "result": { "rest_id": "12", "core": { "screen_name": "jack" }, "legacy": { "followers_count": 1 } } } },
  "legacy": { "full_text": "...", "favorite_count": 10, "retweet_count": 2, "reply_count": 1, "quote_count": 0, "created_at": "...", "id_str": "1790000000000000000" },
  "views": { "count": "1234" }
}

The legacy object uses the same names SocialData uses. The author's handle moved from legacy.screen_name to core.screen_name in 2025, so check both. Long tweets keep their full text in note_tweet.note_tweet_results.result.text. Timelines wrap tweets in instructions and entries; the response format docs explain the structure.

Before and after: Python

Before:

import os, requests
 
r = requests.get(
    "https://api.socialdata.tools/twitter/search",
    headers={"Authorization": f"Bearer {os.environ['SOCIALDATA_KEY']}"},
    params={"query": "from:jack lang:en", "type": "Latest"},
)
data = r.json()
for t in data["tweets"]:
    print(t["id_str"], t["user"]["screen_name"], t["favorite_count"], t["full_text"])
cursor = data.get("next_cursor")

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

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")
for t in tweets(page):
    print(t["id"], t["author"], t["likes"], t["text"])
cursor = extract_bottom_cursor(page)

Before and after: Node.js

Before:

const res = await fetch("https://api.socialdata.tools/twitter/user/12/followers", {
  headers: { Authorization: `Bearer ${process.env.SOCIALDATA_KEY}` },
});
const { users, next_cursor } = await res.json();

After, with the Node SDK:

import { TwittrAPI } from "twittrapi";
import { extractUsers } from "./extract.mjs"; // node/_shared/extract.mjs from the examples repo
 
const client = new TwittrAPI(); // reads TWITTRAPI_API_KEY
 
for await (const page of client.paginate(client.users.followers, { userId: "12", count: 100 }, { maxPages: 5 })) {
  const users = extractUsers(page.data);
  // page.nextCursor is the cursor for the next call
}

Keep your data model: an adapter

If your code reads SocialData's tweet objects, build the same shape from X's JSON in one function:

def to_socialdata_shape(t: dict) -> dict:
    """t is a raw tweet object (tweet_results.result) from X's JSON."""
    legacy = t.get("legacy") or {}
    author = ((t.get("core") or {}).get("user_results") or {}).get("result") or {}
    a_legacy = author.get("legacy") or {}
    a_core = author.get("core") or {}
    note = (((t.get("note_tweet") or {}).get("note_tweet_results") or {}).get("result") or {})
    return {
        "id_str": t.get("rest_id"),
        "full_text": note.get("text") or legacy.get("full_text"),
        "tweet_created_at": legacy.get("created_at"),
        "favorite_count": legacy.get("favorite_count"),
        "retweet_count": legacy.get("retweet_count"),
        "reply_count": legacy.get("reply_count"),
        "quote_count": legacy.get("quote_count"),
        "bookmark_count": legacy.get("bookmark_count"),
        "views_count": int((t.get("views") or {}).get("count") or 0) or None,
        "lang": legacy.get("lang"),
        "user": {
            "id_str": author.get("rest_id"),
            "screen_name": a_core.get("screen_name") or a_legacy.get("screen_name"),
            "name": a_core.get("name") or a_legacy.get("name"),
            "followers_count": a_legacy.get("followers_count"),
            "friends_count": a_legacy.get("friends_count"),
        },
    }

Note one difference in dates: SocialData's tweet_created_at is its own field, while X's legacy.created_at uses X's format (Wed Oct 10 20:19:24 +0000 2018). Parse it if you need ISO dates.

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.
  • Look for the bottom cursor in the response instead of next_cursor. The SDKs' extract_bottom_cursor / extractBottomCursor do this; see Pagination.
  • Stop when there is no bottom cursor, it repeats, or the page holds no items.
  • Set count on timelines and follower lists. X decides how many items it returns.
  • Do not reuse SocialData cursors.

Billing differences to plan for

  • Every successful call is billed, including empty pages. Calls that end in 400, 408, 409 or 5xx are refunded. Responses carry X-Price-Per-Call, X-Remaining-Balance and X-Refunded headers. See Pricing and billing.
  • A page of results is one call, so large pages are cheaper per item.
  • 429 (over your per-second limit) is not charged. Back off and retry; the SDKs do this for you.

Checklist

  • List the SocialData endpoints you call and how often.
  • Check each against the mapping; plan around Spaces, threads, verification checks and webhooks if you use them.
  • Create an account at /start and set TWITTRAPI_API_KEY.
  • Change auth from Authorization: Bearer to X-API-KEY, or install the SDK.
  • Replace POST /twitter/users-by-ids with GET /user/users/by/ids.
  • Write the adapter to your current tweet and user shape; test it on saved responses.
  • Switch pagination to bottom cursors.
  • If you poll, check how often your calls return nothing, and set the interval with per-call billing in mind.
  • Run both side by side on a sample, compare items and cost, then move the rest.

TwittrAPI is not affiliated with X Corp. or SocialData. Reading data needs no X account; optional account actions use your own X session cookie and proxy.

Frequently asked questions

Will my SocialData field names still work?

Mostly, one level down. SocialData's tweet fields (full_text, favorite_count, retweet_count, reply_count, quote_count, id_str, created_at) match the field names inside the legacy object of each tweet in X's JSON, which TwittrAPI passes through. The nesting is different, so you need a small adapter.

Does TwittrAPI charge for empty results?

Yes. A successful call is billed even if X returns no items. On SocialData's per-item endpoints an empty page costs nothing. If you poll often for new tweets, account for that.

What replaces POST /twitter/users-by-ids?

GET /user/users/by/ids?ids=1,2,3, up to 100 IDs per call, billed as one call.

Yes. GET /trends/{locationId} returns X's trends for a location, and GET /misc/locations lists the locations.

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