Skip to content

How to migrate from the Twitter/X API

Migrating off the official X API takes five code changes: map each v2 endpoint to its new path, swap OAuth for an API key, drop fields and expansions, rewrite the JSON parsing and switch the pagination token.

Short answer: Reads are what to move: the official X API bills $0.005 per post and $0.010 per user, and from November 22, 2026 each author returned with a post adds $0.010. To move them, use Sorsa API: one ApiKey header, 20 tweets per search request from $0.07 per 1,000, and 100 free requests to test the map.

Endpoint
POST /search-tweets
Page size
20 tweets per request
Price
from $0.07 per 1,000

#What changed on the official X API since 2023

Code written for the official X API mostly still runs; what changed since 2023 is what it may call, what it gets back and what each response costs. The dated list below comes from X's changelog (opens in a new tab) and its developer forum notices, with the part of an integration each change reaches.

DateChange on the official X APIWhat it does to your code
Mar 30, 2023Paid tiers replace free access; Free becomes write-onlyReads on a free app return errors
Jun 26, 2023Follows endpoints removed from Basic and ProFollower and following calls stop on those plans
Sep 28, 2023v1.1 search/tweets and users/search deprecatedv1.1 search code needs a new endpoint
Jan 19, 2026min_likes, min_replies, min_reposts dropped from searchQueries that used them had to drop them until May 4
Feb 6, 2026Pay-per-use becomes the model; credits bought upfrontCalls are blocked once the balance hits zero
Feb 23, 2026Programmatic replies allowed only when the author summons youReply bots get refused
Apr 20, 2026Following, likes and quote-posts removed from self-serveThose write calls stop working
May 4, 2026Search moves to a new index; retweets leave keyword resultsThe same query returns fewer posts
Jun 1 and Sep 1, 2026Legacy Basic, then Pro plans moved to pay-per-useA monthly cap turns into a per-resource bill
Nov 22, 2026Expansions billed: $0.010 per author, $0.005 per media itemexpansions=author_id can triple the cost of a post
  • Mar 30, 2023

    Change on the official X API
    Paid tiers replace free access; Free becomes write-only
    What it does to your code
    Reads on a free app return errors
  • Jun 26, 2023

    Change on the official X API
    Follows endpoints removed from Basic and Pro
    What it does to your code
    Follower and following calls stop on those plans
  • Sep 28, 2023

    Change on the official X API
    v1.1 search/tweets and users/search deprecated
    What it does to your code
    v1.1 search code needs a new endpoint
  • Jan 19, 2026

    Change on the official X API
    min_likes, min_replies, min_reposts dropped from search
    What it does to your code
    Queries that used them had to drop them until May 4
  • Feb 6, 2026

    Change on the official X API
    Pay-per-use becomes the model; credits bought upfront
    What it does to your code
    Calls are blocked once the balance hits zero
  • Feb 23, 2026

    Change on the official X API
    Programmatic replies allowed only when the author summons you
    What it does to your code
    Reply bots get refused
  • Apr 20, 2026

    Change on the official X API
    Following, likes and quote-posts removed from self-serve
    What it does to your code
    Those write calls stop working
  • May 4, 2026

    Change on the official X API
    Search moves to a new index; retweets leave keyword results
    What it does to your code
    The same query returns fewer posts
  • Jun 1 and Sep 1, 2026

    Change on the official X API
    Legacy Basic, then Pro plans moved to pay-per-use
    What it does to your code
    A monthly cap turns into a per-resource bill
  • Nov 22, 2026

    Change on the official X API
    Expansions billed: $0.010 per author, $0.005 per media item
    What it does to your code
    expansions=author_id can triple the cost of a post

Two of these changed results without changing a line of code. Since the new search index (opens in a new tab) went live on May 4, a keyword search that ran fine in April comes back without retweets. And from November 22, the same request with expansions=author_id that cost $5.00 per 1,000 posts costs up to $15.00, because X's expansions notice (opens in a new tab) bills each returned author as a user read.

The plans changed under running code too. X ended legacy Basic (opens in a new tab) subscriptions at their cutover dates after June 1, 2026, and legacy Pro (opens in a new tab) after September 1; a Pro monthly account was charged a $5,000 credit at cutover, with auto-recharge switched on. Every read now draws on credits at $0.005 per post and $0.010 per user or follower, up to 3M post reads a month, and the full rate card is in our X API pricing breakdown.

#What moves to Sorsa API and what stays on X

Every read of public data can move: search, timelines, post and user lookups, followers, lists, quotes, retweeters, replies, Spaces, communities and trends. Sorsa API, a Twitter (X) API and scraper in production since 2022 with 6B+ requests served, returns all of them through one key, with no OAuth and, as the guide to the Twitter API without a developer account explains, no X developer account. It is read-only by design, so the calls that write stay where they are.

Keep these on the official X API:

  • Posting, replies, deletes and media upload.
  • Direct messages, and likes, follows, bookmarks and blocks, as actions or as lists of your own account's activity.
  • Who liked a post, and anything from protected accounts.
  • Filtered stream, firehose, webhooks and the X Activity API: Sorsa API is polled, not pushed (the polling pattern is below).
  • Post counts, Community Notes and the Ads API.

Your X account takes no part in the reads, because Sorsa API never asks for your login or cookies. After the split, the official X API bills only the writes, per request: $0.015 per post created and $0.20 for a post with a link.

The reads are where the money goes. On search and timeline reads, 1M tweets cost $5,000 on the official X API before any author billing, and from $70 on Sorsa API, which returns each tweet's author inside it at no extra cost. Lookups by ID go further: POST /tweet-info-bulk returns 100 tweets per request, from $0.014 per 1,000.

Tweets read by search

Official X API

$5,000

for 1M tweets

$0.005 per post, before authors

Sorsa API

from$70

for 1M tweets

from $0.07 per 1,000, author included

#Migrate to Sorsa API in six steps

Six steps take a v2 integration from inventory to cutover. The first five are edits to your code; the last one runs for a few days while both APIs answer the same reads.

  1. List every call your code makes. Search the code for api.x.com/2, api.twitter.com/2 and your client's method names, such as Tweepy's search_recent_tweets or xdk's posts.search_recent. Export last month's usage by resource in X's Developer Console, and mark each call as a read or a write.

  2. Get a key and send one request. Sign up, copy the key from the dashboard and call the endpoint that replaces your busiest read.

    Terminal
    curl -X POST "https://api.sorsa.io/v3/search-tweets" \
      -H "ApiKey: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"query": "from:mira_moves_reads", "order": "latest"}'
    
    Response200 OK
    {
      "tweets": [
        {
          "id": "1852331946812457001",
          "full_text": "Read path moved. Writes stay where they were.",
          "created_at": "2026-10-30T14:05:12Z",
          "lang": "en",
          "likes_count": 41,
          "retweet_count": 6,
          "reply_count": 3,
          "quote_count": 1,
          "view_count": 2380,
          "conversation_id_str": "1852331946812457001",
          "is_reply": false,
          "user": {
            "id": "1592041833471827968",
            "username": "mira_moves_reads",
            "display_name": "Mira Moves Reads",
            "followers_count": 912,
            "verified": false
          }
        }
      ],
      "next_cursor": "DAADDAABCgABGbHk7m4WYAEKAAIZsd"
    }
    
  3. Map each read to its endpoint. The table and the converter in the next section pair every v2 read with its Sorsa API path and parameters.

  4. Swap OAuth, fields and expansions for one header. On read calls, delete the bearer token or the OAuth 1.0a signing and send ApiKey: YOUR_API_KEY instead. Drop tweet.fields, user.fields and expansions: every field and the author come back by default.

  5. Rewrite parsing and pagination. Read tweets or users where you read data and includes, rename the fields that differ, and send next_cursor back until it comes back empty.

  6. Shadow-run, then cut over. Send a copy of your reads to Sorsa API for a few days, compare the results, move traffic in stages and keep the X key for writes.

#Map each X API v2 endpoint to Sorsa API

Every public read an integration is likely to call has one Sorsa API endpoint. The table pairs each v2 path, as written in X's OpenAPI specification (opens in a new tab), with its replacement and with how much one request returns; Sorsa API bills per request, not per item, so the last column sets the price.

X API v2 callSorsa API endpointOne request returns
GET /2/tweets/search/recentPOST /search-tweets (opens in a new tab)about 20 tweets
GET /2/tweets/search/allPOST /search-tweets with since: and until:about 20 tweets
GET /2/tweets/:idPOST /tweet-info (opens in a new tab)1 tweet
GET /2/tweets (by IDs)POST /tweet-info-bulk (opens in a new tab)up to 100 tweets
GET /2/users/:id/tweetsPOST /user-tweets (opens in a new tab)about 20 tweets
GET /2/users/:id/mentionsPOST /mentions (opens in a new tab)about 20 tweets
GET /2/tweets/:id/quote_tweetsPOST /quotes (opens in a new tab)about 20 tweets
GET /2/tweets/:id/retweeted_byPOST /retweeters (opens in a new tab)a page of profiles
Search with conversation_id:POST /comments (opens in a new tab)about 20 replies
GET /2/users/by/username/:usernameGET /info (opens in a new tab)1 profile
GET /2/users and GET /2/users/byGET /info-batch (opens in a new tab)up to 100 profiles
GET /2/users/:id/followersGET /followers (opens in a new tab)up to 200 profiles
GET /2/users/:id/followingGET /follows (opens in a new tab)up to 200 profiles
GET /2/users/searchPOST /search-users (opens in a new tab)a page of profiles
GET /2/lists/:id/membersGET /list-members (opens in a new tab)a page of profiles
GET /2/lists/:id/followersGET /list-followers (opens in a new tab)a page of profiles
GET /2/lists/:id/tweetsGET /list-tweets (opens in a new tab)about 20 tweets
GET /2/spaces/:idGET /spaces (opens in a new tab)1 Space
GET /2/communities/:idPOST /community-info (opens in a new tab)1 community
GET /2/communities/searchPOST /search-communities (opens in a new tab)a page of communities
GET /2/trends/by/woeid/:woeidGET /trends (opens in a new tab)1 location's trends
GET /2/usage/tweetsGET /key-usage-info (opens in a new tab)your balance, free of charge

Four habits change with the paths. Tweet endpoints take POST with a JSON body, while user, list and utility lookups stay GET. A username works wherever v2 wanted a numeric ID, so the extra lookup before each timeline call goes away. tweet_link takes a full post URL or a bare ID. And POST /user-tweets keeps paging past the newest 3,200 posts, where X's user timeline stops.

Some v2 reads have no counterpart, because Sorsa API returns only what anyone can see on a public profile: liking users, a user's likes and bookmarks, post counts, the home timeline and every stream. Sorsa API also answers questions v2 can't: POST /check-follow (opens in a new tab) tells in one request whether one account follows another, and GET /about (opens in a new tab) returns an account's country and username history.

The converter below turns one v2 call at a time into its Sorsa API request, with the fields that change and the price of 1,000 results on each side.

Before: official X API v2
curl -G "https://api.x.com/2/tweets/search/recent" \
  -H "Authorization: Bearer $X_BEARER_TOKEN" \
  --data-urlencode "query=from:mira_moves_reads" \
  -d "max_results=100" \
  -d "expansions=author_id" \
  -d "tweet.fields=created_at,public_metrics"
After: Sorsa API
curl -X POST "https://api.sorsa.io/v3/search-tweets" \
  -H "ApiKey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "from:mira_moves_reads", "order": "latest"}'
Sorsa API endpoint
POST /search-tweets
One request returns
about 20 tweets
Official X API, 1,000 results
$5.00, plus up to $10.00 for authors
Sorsa API, 1,000 results
from $0.07

Pagination: next_token becomes next_cursor in the JSON body. The same query searches back to 2006, not 7 days, and every tweet carries its author.

Pick the v2 call your code makes to see the same request on Sorsa API and what 1,000 results cost on each.

If your code uses filtered stream or webhooks

Sorsa API has no stream and no webhook, so a filtered stream rule becomes a search query you poll. Send the rule as a POST /search-tweets query with "order": "latest", add since_id: with the newest ID you have stored, and follow next_cursor so a busy minute with more than 20 new posts loses none of them.

poll.py
import os
import time
import requests

URL = "https://api.sorsa.io/v3/search-tweets"
HEADERS = {"ApiKey": os.environ.get("SORSA_API_KEY", "YOUR_API_KEY")}
RULE = '"open data" lang:en'


def new_tweets(since_id):
    body = {"query": f"{RULE} since_id:{since_id}", "order": "latest"}
    found = []
    while True:
        r = requests.post(URL, headers=HEADERS, json=body, timeout=30)
        if r.status_code == 429:
            time.sleep(1)
            continue
        r.raise_for_status()
        page = r.json()
        found += page.get("tweets", [])
        if not page.get("next_cursor"):
            return found
        body["next_cursor"] = page["next_cursor"]


newest = "1852331946812457001"
while True:
    tweets = new_tweets(newest)
    for t in sorted(tweets, key=lambda t: int(t["id"])):
        print(t["id"], t["user"]["username"], t["full_text"][:60])
    if tweets:
        newest = max((t["id"] for t in tweets), key=int)
    time.sleep(15)

One key runs 20 requests per second on every plan, which is 1,200 single-page polls a minute shared across your rules. Scheduling, alerts and deduplication for monitoring jobs are covered in the guide to real-time Twitter monitoring.

#Rewrite the response parsing

Sorsa API sends each tweet as one object with its author inside, so the join between data and includes disappears and the work left is renaming fields.

Official X API v2 Sorsa API GET /2/tweets/search/recent { "data": [{ "id": "18523319…", "text": "Read path moved…", "author_id": "15920418…", "public_metrics": { "like_count": 41 } }], "includes": {"users": [{ "id": "15920418…", "username": "mira_moves_reads" }]}, "meta": {"next_token": "b26v8…"} } 1 2 3 4 3 5 POST /search-tweets { "tweets": [{ "id": "18523319…", "full_text": "Read path moved…", "user": { "id": "15920418…", "username": "mira_moves_reads" }, "likes_count": 41 }], "next_cursor": "DAADDAAB…" } 1 2 3 4 5 The author rides inside every tweet: no fields or expansions to request.
X API v2 splits a tweet from its author and pages with next_token; Sorsa API puts the author inside the tweet and pages with next_cursor.

The field names below come from X's data dictionary and Sorsa API's live reference, read on November 2, 2026. Most changes are flattening, where a metric leaves public_metrics and keeps its name, and a few are renames.

X API v2 fieldSorsa API field
data[]tweets[], or users[] for lists of accounts
text and note_tweet.textfull_text
author_id with includes.users[]user, the full profile
public_metrics.like_countlikes_count
public_metrics.impression_countview_count
public_metrics.retweet_count, reply_count, quote_count, bookmark_countthe same names, one level up
conversation_idconversation_id_str
referenced_tweets with includes.tweets[]quoted_status, retweeted_status, in_reply_to_tweet_id
attachments.media_keys with includes.media[]entities[] with type, link, preview
name on a userdisplay_name
public_metrics.following_count, tweet_count on a userfollowings_count, tweets_count
meta.next_token and pagination_tokennext_cursor, in both directions
errors[] with title and detail{"message": "..."}
  • data[]

    Sorsa API field
    tweets[], or users[] for lists of accounts
  • text and note_tweet.text

    Sorsa API field
    full_text
  • author_id with includes.users[]

    Sorsa API field
    user, the full profile
  • public_metrics.like_count

    Sorsa API field
    likes_count
  • public_metrics.impression_count

    Sorsa API field
    view_count
  • public_metrics.retweet_count, reply_count, quote_count, bookmark_count

    Sorsa API field
    the same names, one level up
  • conversation_id

    Sorsa API field
    conversation_id_str
  • referenced_tweets with includes.tweets[]

    Sorsa API field
    quoted_status, retweeted_status, in_reply_to_tweet_id
  • attachments.media_keys with includes.media[]

    Sorsa API field
    entities[] with type, link, preview
  • name on a user

    Sorsa API field
    display_name
  • public_metrics.following_count, tweet_count on a user

    Sorsa API field
    followings_count, tweets_count
  • meta.next_token and pagination_token

    Sorsa API field
    next_cursor, in both directions
  • errors[] with title and detail

    Sorsa API field
    {"message": "..."}

IDs stay strings and dates stay ISO 8601, so stored data keeps its shape. Pagination collapses to one name: send next_cursor as a query parameter on GET endpoints or in the JSON body on POST endpoints, and stop when it comes back empty. Rate limiting gets simpler as well. The 15-minute windows and x-rate-limit-* headers described in our guide to X API rate limits give way to one limit of 20 requests per second per key: a 429 means wait a second and retry, and a 403 means the key is missing or the month's requests are used up.

Good to know

Page sizes are not fixed. A search page holds up to 20 tweets and can hold fewer while more pages remain, so loop on next_cursor, never on the number of items, or one short page ends the job early.

#Tweepy and xdk code, before and after

Tweepy and xdk are free libraries; the bill comes from the official X API behind them, so the move swaps the client, not the language. Here is the same search with authors, paged to the end, in Tweepy and then in plain Python on Sorsa API:

import tweepy

client = tweepy.Client(bearer_token="X_BEARER_TOKEN")

for page in tweepy.Paginator(
        client.search_recent_tweets,
        query="from:mira_moves_reads",
        tweet_fields=["created_at", "public_metrics"],
        expansions=["author_id"],
        user_fields=["username", "name"],
        max_results=100):
    users = {u.id: u for u in page.includes.get("users", [])}
    for t in page.data or []:
        author = users[t.author_id]
        print(t.id, author.username, t.public_metrics["like_count"])

The field lists, the includes join and the paginator leave the code; one header comes in. xdk code changes the same way: Client(bearer_token=...) and client.posts.search_recent(...) page through the same data and includes, so they give way to the loop above. The query also reaches further after the move, because POST /search-tweets searches back to 2006 on every plan, where v2's recent search covers the last 7 days.

Code built on the twitter-api-v2 package for Node.js moves the same way, to fetch and one header:

Node.js
const URL = "https://api.sorsa.io/v3/search-tweets";
const headers = {
  ApiKey: process.env.SORSA_API_KEY ?? "YOUR_API_KEY",
  "Content-Type": "application/json",
};

const body = { query: "from:mira_moves_reads", order: "latest" };
do {
  const res = await fetch(URL, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const page = await res.json();
  for (const t of page.tweets ?? []) {
    console.log(t.id, t.user.username, t.likes_count);
  }
  body.next_cursor = page.next_cursor;
} while (body.next_cursor);

For Tweepy code that only reads, move the reads to Sorsa API: search returns 20 tweets per request from $0.07 per 1,000, profiles come 100 per request from $0.014 per 1,000, and no OAuth is needed. Retries, batching and a pacer for 20 requests per second are in the guide to the Twitter API in Python.

#Translate your search queries

POST /search-tweets takes X's Advanced Search syntax, the language of the search box on x.com, so keywords, quoted phrases, from:, to:, lang:, conversation_id: and hashtags carry over unchanged. What changes are v2's is: and has: operators and its time parameters, which move into the query:

X API v2Sorsa API query
start_time and end_time parameterssince:2026-10-01 and until:2026-11-01
since_id and until_id parameterssince_id:ID and max_id:ID (inclusive)
sort_order=recency or relevancy"order": "latest" or "popular" in the body
-is:retweetnothing: retweets stay out by default
is:reply and -is:replyfilter:replies and -filter:replies
is:quotefilter:quote
has:media, has:images, has:linksfilter:media, filter:images, filter:links
has:video_linkfilter:native_video
min_likes:, min_reposts:, min_replies:min_faves:, min_retweets:, min_replies:
point_radius:geocode:
  • start_time and end_time parameters

    Sorsa API query
    since:2026-10-01 and until:2026-11-01
  • since_id and until_id parameters

    Sorsa API query
    since_id:ID and max_id:ID (inclusive)
  • sort_order=recency or relevancy

    Sorsa API query
    "order": "latest" or "popular" in the body
  • -is:retweet

    Sorsa API query
    nothing: retweets stay out by default
  • is:reply and -is:reply

    Sorsa API query
    filter:replies and -filter:replies
  • is:quote

    Sorsa API query
    filter:quote
  • has:media, has:images, has:links

    Sorsa API query
    filter:media, filter:images, filter:links
  • has:video_link

    Sorsa API query
    filter:native_video
  • min_likes:, min_reposts:, min_replies:

    Sorsa API query
    min_faves:, min_retweets:, min_replies:
  • point_radius:

    Sorsa API query
    geocode:

Three details trip up translated queries. Native retweets are left out unless you add include:nativeretweets. geocode: takes latitude first, as in geocode:37.77,-122.41,5km, where v2's point_radius: took longitude first. And max_id: includes the ID itself, while v2's until_id excludes it. The full operator list, with recipes, is in our guide to Twitter search operators.

#Test with a shadow run, then cut over

A shadow run sends a copy of each read to Sorsa API while the official X API keeps serving your app, so the results are compared before anything depends on them.

Stage 1 Shadow X API serves every read; Sorsa API gets a copy Reads X API 100% Sorsa API copy Stage 2 Compare Match post IDs, fields and errors for each query Reads X API 100% Sorsa API copy Stage 3 Canary 10% of reads come from Sorsa API; watch the errors Reads X API 90% Sorsa API 10% Stage 4 Cut over Every read runs on Sorsa API; delete the old read code Reads Sorsa API 100% Writes Official X API in every stage: posts, replies, DMs
Reads move in four stages; writes never leave the official X API.

The script runs one query over the same closed day on both APIs and compares the post IDs. Run it for each query and timeline you depend on; a closed window keeps new posts from skewing the count.

shadow_run.py
import os
import requests

X_URL = "https://api.x.com/2/tweets/search/recent"
X_HEADERS = {"Authorization": "Bearer " + os.environ["X_BEARER_TOKEN"]}
S_URL = "https://api.sorsa.io/v3/search-tweets"
S_HEADERS = {"ApiKey": os.environ.get("SORSA_API_KEY", "YOUR_API_KEY")}


def x_ids(query, start, end):
    params = {"query": query, "start_time": start,
              "end_time": end, "max_results": 100}
    ids = set()
    while True:
        r = requests.get(X_URL, headers=X_HEADERS, params=params, timeout=30)
        r.raise_for_status()
        page = r.json()
        ids |= {t["id"] for t in page.get("data", [])}
        token = page.get("meta", {}).get("next_token")
        if not token:
            return ids
        params["next_token"] = token


def sorsa_ids(query, since, until):
    body = {"query": f"{query} since:{since} until:{until}",
            "order": "latest"}
    ids = set()
    while True:
        r = requests.post(S_URL, headers=S_HEADERS, json=body, timeout=30)
        r.raise_for_status()
        page = r.json()
        ids |= {t["id"] for t in page.get("tweets", [])}
        if not page.get("next_cursor"):
            return ids
        body["next_cursor"] = page["next_cursor"]


query = "from:mira_moves_reads"
a = x_ids(query, "2026-10-30T00:00:00Z", "2026-10-31T00:00:00Z")
b = sorsa_ids(query, "2026-10-30_00:00:00_UTC", "2026-10-31_00:00:00_UTC")
print(f"official X API {len(a)}, Sorsa API {len(b)}, shared {len(a & b)}")
print("only on X:", sorted(a - b)[:10])
print("only on Sorsa API:", sorted(b - a)[:10])

Expect small gaps rather than zero: posts deleted between the two calls drop out, and X's keyword search has left out retweets since May 4, 2026. X's recent search reaches back 7 days, so keep the window inside the last week. Once the IDs line up for the reads you run most, cut over in stages:

  • Put both clients behind one interface and one config flag, so any read type can move back with a single value.
  • Move 10% of reads, watch errors and GET /key-usage-info, which is free and never counts against your requests, then move the rest.
  • Keep the official X API key for writes only, and lower its spending limit and auto-recharge amount in the Developer Console.
  • Delete the read code paths, the field lists and the old pagination once a full billing cycle has passed.

#Move your reads off the official X API

A read-only v2 integration moves to Sorsa API, the Twitter API built for reading public X data, with one header, one endpoint per call and a parser that loses its join. Search runs from $0.07 per 1,000 tweets instead of $5.00, followers from $0.007 per 1,000 instead of $10.00, and every plan answers 20 requests per second. Start with your most expensive read, shadow-run it for a few days, and keep the official X API only for what writes.

#FAQ

Which Twitter API should I migrate to from the official X API?

For reading public X data, migrate to Sorsa API: each v2 read maps to one endpoint, tweets cost from $0.014 per 1,000 by ID and from $0.07 by search against $5.00 on the official X API, every plan runs at 20 requests per second, and search reaches back to 2006. One API key replaces OAuth. Sorsa API is read-only, so keep the official X API for posting.

How long does a migration off the official X API take?

The code change grows with the number of distinct v2 calls, not with your data volume: each call becomes one endpoint, one header and a few renamed fields. Add a shadow run of a few days in which both APIs answer the same reads and you compare the IDs. Posting, DMs and other writes stay on the official X API, so only the read path moves.

Can I keep posting to X after moving reads to Sorsa API?

Yes. Keep the official X API for posting, replies and DMs, and send reads to Sorsa API. Sorsa API is read-only by design: its key can't act on any X account and never needs your X login, so the two sit side by side without conflict. If your write path still signs with OAuth 1.0a user tokens, X has let apps exchange them for OAuth 2.0 tokens since September 21, 2026.

Do I need to migrate from v1.1 to v2 first?

No. Code that still calls v1.1 reads can map straight to Sorsa API: statuses/user_timeline becomes POST /user-tweets, statuses/show becomes POST /tweet-info, and users/show becomes GET /info. X lists v1.1 as legacy with limited support and deprecated its v1.1 search in 2023, so a stop on v2 means rewriting for OAuth 2.0 and pay-per-use billing before you move again.

What happened to legacy Basic and Pro X API plans?

X moved them to pay-per-use: legacy Basic subscriptions ended at their cutover dates after June 1, 2026, and Pro after September 1, 2026. A Pro monthly account was charged a $5,000 credit at cutover, with auto-recharge on. Reads now bill per resource, $0.005 per post and $0.010 per user, while the same tweets cost from $0.07 per 1,000 by search on Sorsa API.

#How we checked

On November 2, 2026 we built the endpoint map by comparing X's own OpenAPI specification (version 2.173, 200 paths) with Sorsa API's live reference (45 endpoints), and checked every v2 operator against X's operator list and every replacement against Sorsa API's search operators. The same day we read X's changelog, pricing page, pagination and rate-limit docs, the Python XDK reference and Tweepy 4.17.0's client, plus X's forum notices on the search index, legacy plans and expansion billing. We had no Sorsa API key for this pass, so the response shown uses demo values; we ran the Python and Node.js blocks against a local mock that answers in both APIs' documented shapes.

By the Sorsa API team. We build and run Sorsa API, the read-only Twitter/X API and scraper, in production since 2022 (formerly TweetScout API) with 6B+ requests served. About Sorsa API

What changed
  • Rewritten from scratch as a migration guide: X's 2023-2026 changes and what each one breaks, a v2-to-Sorsa API endpoint map from both APIs' specs, a request converter, the field and operator maps, Tweepy code before and after, polling in place of streams, and a shadow-run test plan.
  • First published.