Skip to content

Export Twitter data to Google Sheets

Google Sheets can't pull Twitter data by itself, so tweets and followers reach a sheet through an API called from Apps Script on a timer; one spreadsheet holds up to 20 million cells, about 2 million tweets.

Short answer: Every route to a sheet runs on an API that returns JSON. The official X API bills $0.005 per post and $0.010 per follower and needs a developer app. For Google Sheets, use Sorsa API from Apps Script: one ApiKey header, 20 tweets or 200 followers per request, from $0.007 per 1,000 followers, and 100 free requests to start.

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

#How Twitter data gets into Google Sheets

Twitter data gets into Google Sheets in one of four ways, and all four need a data source that returns JSON: an Apps Script that calls an API on a timer, a connector add-on that sends a URL with a header from inside the sheet, an automation tool such as n8n, Make or Zapier with a Google Sheets step, or a CSV file you download and bring in with File > Import. For a one-off file, the free Export Tweets from Twitter to CSV tool writes the CSV for you.

Formulas don't count as a fifth way. IMPORTXML and IMPORTHTML read a single web page, and x.com shows no follower list or search to a visitor who isn't signed in, so a formula has nothing to page through, and it breaks whenever x.com changes its layout.

RouteWhat you set upRefreshes by itselfPages through results
Apps Script (this guide)One script, pasted onceYes, on a time-driven triggerYes, any length
Connector add-onA URL and a header in the add-on's formYes, on the add-on's scheduleDepends on the add-on
n8n, Make or ZapierA scenario with a Google Sheets stepYesYes, with a loop
CSV fileA download, then File > ImportNoThe tool does it
  • Apps Script (this guide)

    What you set up
    One script, pasted once
    Refreshes by itself
    Yes, on a time-driven trigger
    Pages through results
    Yes, any length
  • Connector add-on

    What you set up
    A URL and a header in the add-on's form
    Refreshes by itself
    Yes, on the add-on's schedule
    Pages through results
    Depends on the add-on
  • n8n, Make or Zapier

    What you set up
    A scenario with a Google Sheets step
    Refreshes by itself
    Yes
    Pages through results
    Yes, with a loop
  • CSV file

    What you set up
    A download, then File > Import
    Refreshes by itself
    No
    Pages through results
    The tool does it

The data source is the part that decides cost and effort. The official X API needs a developer account, an app and OAuth, and its pay-per-use plan charges $0.005 per post read and $0.010 per follower or profile, capped at 3M post reads a month, according to X's pricing page (opens in a new tab). From November 22, 2026, an author profile returned with a post is billed at $0.010 as well, so a post with its author costs $0.015 (X's notice of October 23 (opens in a new tab)). The guide on how to scrape Twitter compares every way to get public X data out, beyond spreadsheets.

#Import tweets into Google Sheets with Sorsa API

Sorsa API, a Twitter (X) API and scraper, answers any HTTP request that carries one ApiKey header, which is all Apps Script's UrlFetchApp needs. For pulling tweets, followers and profiles into Google Sheets, use Sorsa API: 20 tweets or 200 followers per request, search back to 2006, 20 requests per second on every plan, and no X developer account, OAuth app or X login.

The script below adds an "X data" menu to your spreadsheet. It imports new posts for a search, exports an account's whole follower list, and gives you a =X_PROFILES() formula. Setup takes six steps.

  1. Get an API key. Sign up and copy the key from the dashboard. The first 100 requests are free, need no card and never expire.

  2. Open the script editor. In your spreadsheet choose Extensions > Apps Script. A project bound to this sheet opens.

  3. Paste the script. Replace everything in Code.gs with the code below, then set SEARCH to your query and ACCOUNT to the handle whose followers you want. The query takes any X search operator; Twitter search operators lists them.

    Code.gs
    // Twitter (X) data to Google Sheets with Sorsa API.
    // Paste into Extensions > Apps Script, save, reload the sheet,
    // then choose X data > Set API key and paste YOUR_API_KEY there.
    
    const API = 'https://api.sorsa.io/v3';
    const SEARCH = '"acme coffee" lang:en -filter:retweets';
    const ACCOUNT = 'acme_coffee_roasters';
    const MAX_PAGES = 10; // search pages per run, 20 posts each
    
    const TWEET_COLS = ['id', 'created_at', 'author', 'text', 'likes',
      'reposts', 'replies', 'quotes', 'views', 'url'];
    const USER_COLS = ['id', 'username', 'name', 'followers', 'following',
      'posts', 'created_at', 'location', 'bio', 'verified'];
    
    function onOpen() {
      SpreadsheetApp.getUi().createMenu('X data')
        .addItem('Set API key', 'setApiKey')
        .addItem('Import new search results', 'importSearch')
        .addItem('Export followers', 'exportFollowers')
        .addToUi();
    }
    
    function setApiKey() {
      const ui = SpreadsheetApp.getUi();
      const answer = ui.prompt('Paste your Sorsa API key');
      if (answer.getSelectedButton() !== ui.Button.OK) return;
      PropertiesService.getUserProperties()
        .setProperty('SORSA_API_KEY', answer.getResponseText().trim());
    }
    
    // One request: GET params go in the URL, POST params in a JSON body.
    function sorsa_(method, path, params) {
      const key = PropertiesService.getUserProperties()
        .getProperty('SORSA_API_KEY');
      if (!key) throw new Error('Run X data > Set API key first.');
      const options = { method: method, headers: { ApiKey: key },
        muteHttpExceptions: true };
      let url = API + path;
      if (method === 'get') {
        url += '?' + Object.keys(params).map((k) => [].concat(params[k])
          .map((v) => k + '=' + encodeURIComponent(v)).join('&')).join('&');
      } else {
        options.contentType = 'application/json';
        options.payload = JSON.stringify(params);
      }
      for (let attempt = 1; attempt <= 3; attempt++) {
        const res = UrlFetchApp.fetch(url, options);
        const code = res.getResponseCode();
        if (code === 200) return JSON.parse(res.getContentText());
        if (code !== 429) throw new Error(code + ' ' + res.getContentText());
        Utilities.sleep(1000 * attempt); // over 20 requests a second
      }
      throw new Error('429 three times in a row');
    }
    
    function sheet_(name, cols) {
      const book = SpreadsheetApp.getActiveSpreadsheet();
      const sh = book.getSheetByName(name) || book.insertSheet(name);
      if (sh.getLastRow() === 0) {
        sh.getRange('A:A').setNumberFormat('@'); // 19-digit IDs stay text
        sh.appendRow(cols);
        sh.setFrozenRows(1);
      }
      return sh;
    }
    
    function append_(sh, rows) {
      if (!rows.length) return;
      sh.getRange(sh.getLastRow() + 1, 1, rows.length, rows[0].length)
        .setValues(rows);
    }
    
    // Text that starts with = + or - would be read as a formula.
    function safe_(s) {
      s = s || '';
      return /^[=+-]/.test(s) ? "'" + s : s;
    }
    
    function tweetRow_(t) {
      const u = t.user || {};
      return [t.id, new Date(t.created_at), '@' + u.username,
        safe_(t.full_text), t.likes_count || 0, t.retweet_count || 0,
        t.reply_count || 0, t.quote_count || 0, t.view_count || 0,
        'https://x.com/' + u.username + '/status/' + t.id];
    }
    
    function userRow_(u) {
      return [u.id, u.username, safe_(u.display_name), u.followers_count,
        u.followings_count, u.tweets_count, new Date(u.created_at),
        safe_(u.location), safe_(u.description), u.verified];
    }
    
    // New posts for SEARCH, newest first, until the first ID already in
    // the sheet. They are appended oldest first, so the sheet reads as a log.
    function importSearch() {
      const sh = sheet_('Search', TWEET_COLS);
      const last = sh.getLastRow();
      const seen = new Set(last > 1 ?
        sh.getRange(2, 1, last - 1, 1).getDisplayValues().flat() : []);
      const rows = [];
      let cursor = null;
      pages: for (let page = 0; page < MAX_PAGES; page++) {
        const body = { query: SEARCH, order: 'latest' };
        if (cursor) body.next_cursor = cursor;
        const res = sorsa_('post', '/search-tweets', body);
        for (const t of res.tweets || []) {
          if (seen.has(t.id)) break pages;
          seen.add(t.id);
          rows.push(tweetRow_(t));
        }
        cursor = res.next_cursor;
        if (!cursor) break;
      }
      append_(sh, rows.reverse());
    }
    
    // Every follower of ACCOUNT, 200 per request. Each run stops after five
    // minutes, under the six-minute limit, and the next run resumes from
    // the saved cursor. Delete FOLLOWERS_CURSOR to start a new export.
    function exportFollowers() {
      const props = PropertiesService.getUserProperties();
      let cursor = props.getProperty('FOLLOWERS_CURSOR');
      if (cursor === 'done') return;
      const sh = sheet_('Followers', USER_COLS);
      const stopAt = Date.now() + 5 * 60 * 1000;
      while (Date.now() < stopAt) {
        const params = { username: ACCOUNT };
        if (cursor) params.next_cursor = cursor;
        const res = sorsa_('get', '/followers', params);
        append_(sh, (res.users || []).map(userRow_));
        cursor = res.next_cursor;
        props.setProperty('FOLLOWERS_CURSOR', cursor || 'done');
        if (!cursor) return;
      }
    }
    
    /**
     * Profiles for up to 100 handles from one request: =X_PROFILES(A2:A101)
     * @param {string[][]} handles A column of X handles, with or without @.
     * @return {Array[]} One row per handle, in the same order.
     * @customfunction
     */
    function X_PROFILES(handles) {
      const names = [].concat(handles).flat()
        .map((h) => String(h).trim().replace(/^@/, '')).slice(0, 100);
      const ask = names.filter((h) => h);
      if (!ask.length) return '';
      const res = sorsa_('get', '/info-batch', { usernames: ask });
      const found = {};
      for (const u of res.users || []) found[u.username.toLowerCase()] = u;
      const blank = USER_COLS.map(() => '');
      return names.map((h) => found[h.toLowerCase()] ?
        userRow_(found[h.toLowerCase()]) : blank);
    }
    
    // Run once: search every hour, followers every 10 minutes until done.
    function createTriggers() {
      ScriptApp.getProjectTriggers().forEach((t) => ScriptApp.deleteTrigger(t));
      ScriptApp.newTrigger('importSearch').timeBased().everyHours(1).create();
      ScriptApp.newTrigger('exportFollowers').timeBased().everyMinutes(10)
        .create();
    }
    
  4. Save and reload the sheet. Press Ctrl+S (Cmd+S on a Mac), close the editor and reload the spreadsheet tab. The "X data" menu appears next to Help.

  5. Store the key. Choose X data > Set API key and paste your key. The script keeps it in your user properties, so it never sits in a cell, in the code or in a copy of the sheet you share.

  6. Run the first import. Choose X data > Import new search results and allow the two permissions Google asks for: this spreadsheet and connections to an external service. A Search tab appears with up to 200 posts, oldest at the top.

    Trigger every hour, or the X data menu Apps Script UrlFetchApp with an ApiKey header request JSON Sorsa API 20 tweets per request 200 followers per request 100 profiles per request Google Sheet one write per page, IDs stored as text, newest rows only
    One header, one request per page, one write per page: the whole pipeline runs inside your spreadsheet.

The script's search calls POST /search-tweets (opens in a new tab) with order set to latest. Each response holds up to 20 posts, every one with its author's full profile, and a next_cursor for the next page. A cursor is a token that points to the next page of results; the last page comes back without one. To fill a sheet with one account's posts instead of a search, call POST /user-tweets (opens in a new tab) with a username in the body; the guide to download all tweets from a user covers that endpoint in depth.

Response200 OK
{
  "tweets": [
    {
      "id": "2116630142857191424",
      "full_text": "Our Ethiopia natural roast is back on the menu.",
      "created_at": "2026-11-01T14:03:00Z",
      "lang": "en",
      "likes_count": 42,
      "retweet_count": 5,
      "reply_count": 3,
      "quote_count": 1,
      "view_count": 3810,
      "bookmark_count": 2,
      "user": {
        "id": "1500000000000000004",
        "username": "acme_coffee_roasters",
        "display_name": "Acme Coffee Roasters",
        "followers_count": 12840
      }
    }
  ],
  "next_cursor": "DAACCgACG3eLqJBAJxAKAAMbd4uokD-nGAgABAAAAAIAAA"
}

#What lands in each column

Each post becomes one row of ten columns, and three of them need care that a plain copy of the JSON would get wrong. The table maps every column to its field in the response.

ColumnField in the responseWhat the script does
ididWrites it into a column formatted as plain text
created_atcreated_atTurns the ISO 8601 string into a real date
authoruser.usernameAdds the @
textfull_textAdds an apostrophe before a leading =, + or -
likes, reposts, replies, quotes, viewslikes_count, retweet_count, reply_count, quote_count, view_countWrites 0 where a count is missing
urlbuilt from user.username and idLinks to the post on x.com
  • id

    Field in the response
    id
    What the script does
    Writes it into a column formatted as plain text
  • created_at

    Field in the response
    created_at
    What the script does
    Turns the ISO 8601 string into a real date
  • author

    Field in the response
    user.username
    What the script does
    Adds the @
  • text

    Field in the response
    full_text
    What the script does
    Adds an apostrophe before a leading =, + or -
  • likes, reposts, replies, quotes, views

    Field in the response
    likes_count, retweet_count, reply_count, quote_count, view_count
    What the script does
    Writes 0 where a count is missing
  • url

    Field in the response
    built from user.username and id
    What the script does
    Links to the post on x.com

The three traps, each one a known cause of broken sheets:

  • Tweet IDs lose digits as numbers. An X post ID has 19 digits and Google Sheets keeps 15 significant digits, so a numeric cell turns the last four into zeros and the ID no longer points to the post. The script formats column A as plain text before the first row is written.
  • Post text can look like a formula. A post that starts with "=", "+" or "-" is read by Sheets as a formula and shows an error. The script puts an apostrophe in front, which Sheets hides and keeps the text as text.
  • Dates should sort as dates. The API returns created_at as ISO 8601, so new Date() reads it directly, and the column sorts, filters and charts as a date.

Duplicates never reach the sheet. Before each search the script reads the IDs already in column A, asks for the newest posts first and stops at the first post it has already saved, so a run that finds nothing new costs one request.

#Keep the sheet updated on a schedule

A time-driven trigger runs the import by itself, and createTriggers() sets two up in one go: the search every hour and the follower export every 10 minutes until it finishes. Open the script editor, pick createTriggers in the function list and press Run once. Google runs the functions as you, with your key, whether or not the sheet is open.

An hourly search that finds one page of new posts uses 720 requests a month, 7% of the 10K on the Starter plan. Apps Script itself is free, and an hourly run sits far inside its limits on a free Google account. These are the limits from Google's Apps Script quotas page (opens in a new tab) that shape the script:

LimitFree Google accountGoogle Workspace
URL Fetch calls a day20K100K
Run time of one execution6 minutes6 minutes
Trigger run time a day90 minutes6 hours
Run time of a custom function30 seconds30 seconds
  • URL Fetch calls a day

    Free Google account
    20K
    Google Workspace
    100K
  • Run time of one execution

    Free Google account
    6 minutes
    Google Workspace
    6 minutes
  • Trigger run time a day

    Free Google account
    90 minutes
    Google Workspace
    6 hours
  • Run time of a custom function

    Free Google account
    30 seconds
    Google Workspace
    30 seconds

Time-driven triggers can also run every 1, 5, 10, 15 or 30 minutes, or once a day at an hour you pick. Google emails you when a triggered run fails, with the error the script threw.

Good to know

The search stops after MAX_PAGES pages, 200 posts by default. If a busy query gets more than 200 new posts between two runs, the oldest of them are skipped. Raise MAX_PAGES or run the trigger more often for brand names and hashtags with heavy traffic.

#Export a whole follower list into Google Sheets

To export every follower of an account into Google Sheets, page through GET /followers (opens in a new tab) 200 profiles at a time and append each page; a list of 10K followers takes 50 requests. Choose X data > Export followers, and the script fills a Followers tab with each follower's ID, handle, name, follower and following counts, post count, join date, location, bio and verified flag.

Long lists don't fit in one run, and the script is built for that. Apps Script stops any execution at 6 minutes, so exportFollowers() works for five, saves the cursor in your user properties and returns. The next run, from the menu or from the 10-minute trigger, starts at that cursor, and the run that gets a page without a cursor marks the export done. A list of 300K followers is 1,500 pages of 200 and fills the tab over as many runs as it takes, with no page fetched twice.

Run 1 up to 5 minutes 200 200 ... followers per page cursor saved Run 2 10 minutes later 200 200 ... starts at the cursor Last run no next_cursor done later runs stop 300K followers = 1,500 pages of 200, written to the Followers tab over as many runs as it takes. Each run ends under the 6-minute Apps Script limit.
The saved cursor turns a 6-minute limit into an export of any length.

Followers are where the price gap is widest. The official X API charges $0.010 for each follower it returns, so a 100K list costs $1,000; Sorsa API returns the same list from $0.70, from $0.007 per 1,000 followers. Your 100 free requests cover the first 20K followers. To save a follower list as a CSV or Excel file instead of a sheet, see how to export Twitter followers.

#Fill a profile table with one formula

=X_PROFILES(A2:A101) turns a column of up to 100 handles into a table of profiles with one request. Type the handles in column A, with or without the @, and put the formula in B2: the rows spill to the right and down, one per handle, in the same order, with a blank row where an account doesn't exist. The function calls GET /info-batch (opens in a new tab), which returns up to 100 profiles per request, from $0.014 per 1,000.

A custom function follows Google's rules for them, listed in Google's custom functions guide (opens in a new tab):

  • It must finish within 30 seconds, which one request does with room to spare.
  • It runs again only when the handles in the range change. To refresh the counts, edit a cell in the range or re-enter the formula.
  • It reads the key from the spreadsheet owner's user properties, so it works for everyone who can edit the sheet without sharing the key.
  • Never pass it NOW() or RAND(): Google leaves a custom function with a volatile argument on "Loading..." for good.

For a dated history of follower counts, keep the formula for the live view and copy its rows into a log tab on a daily trigger.

#What it costs, and how much one sheet holds

Sorsa API charges per request, not per row, so the price of a sheet depends on how many rows each request returns, and every kind of row costs a fraction of the official X API's rate. The table shows the price per 1,000 rows for each kind of data the script writes.

Data in the sheetSorsa API, per 1,000 rowsRows per requestOfficial X API, per 1,000 rows
Tweets from a search or a timelinefrom $0.0720$5.00
Followers of an accountfrom $0.007200$10.00
Profiles from a list of handlesfrom $0.014100$10.00
  • Tweets from a search or a timeline

    Sorsa API, per 1,000 rows
    from $0.07
    Rows per request
    20
    Official X API, per 1,000 rows
    $5.00
  • Followers of an account

    Sorsa API, per 1,000 rows
    from $0.007
    Rows per request
    200
    Official X API, per 1,000 rows
    $10.00
  • Profiles from a list of handles

    Sorsa API, per 1,000 rows
    from $0.014
    Rows per request
    100
    Official X API, per 1,000 rows
    $10.00

Google's side has a ceiling too. A spreadsheet holds up to 20 million cells, according to Google Drive's file limits (opens in a new tab), so at the script's ten columns one spreadsheet takes just under 2 million rows. Pick a kind of data and a number of rows to see both bills and how much of a sheet they fill.

Data for the sheet
Rows

Official X API

$500

$0.005 per post

Sorsa API

from $7

from $0.07 per 1,000 tweets, 20 per request

100K tweets at 10 columns fill 1M of one sheet's 20M cells (5%). Your 100 free requests cover the first 2,000.

Both bills for the same rows, and the share of one spreadsheet they use at ten columns.

Failed requests are never charged, and the plans differ only in how many requests they include each month.

#Use a connector add-on, n8n, Make or Zapier instead

Sorsa API works with any tool that can send an HTTPS request with a custom header, so the no-code routes need no script. There is no Sorsa API add-on for Google Sheets; a general connector add-on from the Google Workspace Marketplace does the job when you give it three things:

  • the URL, for example https://api.sorsa.io/v3/followers?username=acme_coffee_roasters;
  • a header named ApiKey with your key as its value, never the key in the URL;
  • the users or tweets array as the data to write, and next_cursor as the pagination token, if the add-on pages through results.

Automation tools take the same request. The guide to the Twitter API in n8n builds a workflow from a Schedule Trigger, an HTTP Request node with a Header Auth credential, a Split Out node and a Google Sheets node that appends rows. Make's HTTP module and Zapier's Custom Request action send the same request; the guides to the Twitter API in Make and Zapier cover their setups.

For a one-time copy with no tool at all, download the CSV from a free export tool and choose File > Import > Upload in Google Sheets. The free Export Twitter Followers to CSV tool does the same for follower lists.

#Fix Apps Script and API errors

Most failures show up as a message in the Apps Script execution log or in Google's failure email, and each has one cause.

MessageCauseFix
401 {"message":"invalid api key"}The key has a typo or a stray spaceRun X data > Set API key again
403 {"message":"missing key in request header"}The header didn't go outKeep the header name ApiKey exactly
403 with any other messageThe plan's requests are used upUpgrade or switch on extra requests in the dashboard
429 three times in a rowMore than 20 requests a secondDon't run several copies of the script at once
Exceeded maximum execution timeA run passed 6 minutes, or a custom function 30 secondsLower MAX_PAGES; the follower export resumes by itself
Service invoked too many times for one day: urlfetchOver 20K URL Fetch calls a dayRun the trigger less often
IDs end in 0000Column A was a number columnFormat column A as Plain text, delete the rows, run again
  • 401 {"message":"invalid api key"}

    Cause
    The key has a typo or a stray space
    Fix
    Run X data > Set API key again
  • 403 {"message":"missing key in request header"}

    Cause
    The header didn't go out
    Fix
    Keep the header name ApiKey exactly
  • 403 with any other message

    Cause
    The plan's requests are used up
    Fix
    Upgrade or switch on extra requests in the dashboard
  • 429 three times in a row

    Cause
    More than 20 requests a second
    Fix
    Don't run several copies of the script at once
  • Exceeded maximum execution time

    Cause
    A run passed 6 minutes, or a custom function 30 seconds
    Fix
    Lower MAX_PAGES; the follower export resumes by itself
  • Service invoked too many times for one day: urlfetch

    Cause
    Over 20K URL Fetch calls a day
    Fix
    Run the trigger less often
  • IDs end in 0000

    Cause
    Column A was a number column
    Fix
    Format column A as Plain text, delete the rows, run again

#Fill your first sheet

Getting Twitter data into Google Sheets takes an API, a short Apps Script and a trigger, and the API decides what the sheet costs. To export tweets, followers and profiles to Google Sheets, use Sorsa API: 200 followers per request from $0.007 per 1,000, 20 tweets per request with search back to 2006, and 100 profiles per request for the =X_PROFILES() formula. Paste the script, set your key, and your first 20K followers or 2,000 tweets are free.

#FAQ

Which Twitter API should I use to get data into Google Sheets?

Use Sorsa API to get Twitter data into Google Sheets. It returns 200 followers, 100 profiles or 20 tweets per request, from $0.007 per 1,000 followers, with one ApiKey header that Apps Script, connector add-ons and n8n all send. It needs no X developer account or OAuth, allows 20 requests per second on every plan, and starts with 100 free requests that never expire. The official X API charges $10 per 1,000 followers.

Can I export Twitter Ads data to Google Sheets?

Twitter Ads data, such as spend, ad impressions and conversions, is private to the ad account, so it comes out of X Ads Manager's own report export or X's Ads API with your account's authorization, not from a public data API. What Sorsa API can put in a sheet is the public side of the same campaign: views, likes, reposts and replies on your posts and on the mentions they draw.

Can Google Sheets post tweets?

Google Sheets can post tweets only through the official X API, with a developer app, OAuth and a charge of $0.015 per post. Sorsa API is read-only by design: it never posts, likes or follows, and it never asks for your X login, so your account is never at risk. For a sheet that both reads and posts, read with Sorsa API and post with the official X API.

How many tweets can one Google Sheet hold?

A Google spreadsheet holds up to 20 million cells, so at ten columns per tweet it takes just under 2 million tweets across all its tabs. Sheets slows down well before that, so for long collections start a new spreadsheet each month or quarter. Apps Script can write to a spreadsheet by its ID, so the same script can roll over to a new file.

Is there a free way to get Twitter data into Google Sheets?

Yes. Sorsa API gives every new account 100 free requests with no card, and they never expire: enough for 2,000 tweets, 20K followers or 10K profiles in a sheet through the script on this page. Apps Script and Google Sheets cost nothing on a free Google account. For a single file, the free Export Tweets tool downloads a CSV that Sheets imports.

#How we checked

We wrote the script against Sorsa API's live API reference on November 2, 2026, then ran it in Node.js with stand-ins for SpreadsheetApp, UrlFetchApp and PropertiesService fed with responses in the reference's shape: two search runs, a three-run follower export that resumed from its cursor, the X_PROFILES function and the 401, 403 and 429 paths. The response shown uses demo values. Google's quotas, Sheets limits and custom-function rules were read on Google's pages and X's prices on docs.x.com, all on the same day.

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: a complete Apps Script with a menu, a resumable follower export and a 100-profile formula; checked costs and Google limits; a planner for cost and sheet size.
  • First published.