XActions API Reference

September 14, 2026 · View on GitHub

Function reference for the xactions npm package.

npm install xactions

Table of Contents


Two ways in

The package ships two independent scraping paths, and picking the wrong one is the most common reason a first script returns nothing.

PathImportBrowserLogin
HTTP client (recommended, what the CLI uses)xactions/clientnonenot needed for profiles and public timelines
Puppeteer scrapersxactions or xactions/scrapersChromiumrequired, X serves a logged-out browser an empty page

Every Puppeteer function below takes a page as its first argument and expects that page to be logged in via loginWithCookie. If you just want data, start with the HTTP client.


HTTP client (no browser)

import { Scraper, SearchMode } from 'xactions/client';

const x = new Scraper();

// Guest tier: no cookies required.
const profile = await x.getProfile('NASA');
console.log(profile.name, profile.followersCount);

for await (const tweet of x.getTweets('NASA', 20)) {
  console.log(tweet.likes, tweet.text);
}

Reads that return lists are async generators, so they page lazily and you stop whenever you like. Session-tier reads (search, followers, following, likes, bookmarks, DMs) need the two cookies a logged-in browser holds:

const x = new Scraper();
await x.setCookies(`auth_token=${process.env.X_AUTH_TOKEN}; ct0=${process.env.X_CSRF_TOKEN}`);

for await (const tweet of x.searchTweets('ai agents', 50, SearchMode.Latest)) {
  console.log(tweet.text);
}

// Or reuse the session `xactions login` already saved.
await x.loadCookies(`${process.env.HOME}/.xactions/cookies.json`);

Scraper methods: getProfile, me, getFollowers, getFollowing, getTweet, getTweets, getTweetsAndReplies, getLikedTweets, getLatestTweet, searchTweets, searchProfiles, getTrends, getExploreTabs, getListTweets, getListMembers, getListById, getDmConversations, getDmMessages, plus the writes sendTweet, sendQuoteTweet, deleteTweet, likeTweet, unlikeTweet, retweet, unretweet, followUser, unfollowUser, sendDm, sendDmToUser, and the session helpers login, logout, isLoggedIn, getCookies, setCookies, saveCookies, loadCookies.


Core Functions

createBrowser(options?)

Launch a Puppeteer browser with stealth mode enabled (avoids bot detection).

Parameters:

ParamTypeDefaultDescription
options.headlessbooleantrueRun in headless mode
options.proxystringnoneHTTP(S) proxy URL
options.userDataDirstringnonePersistent browser profile directory
options.argsstring[]noneAdditional Chrome flags

Returns: Promise<Browser>

// Headless (default)
const browser = await createBrowser();

// With visible browser
const browser = await createBrowser({ headless: false });

// With proxy
const browser = await createBrowser({ proxy: 'http://user:pass@proxy:8080' });

createPage(browser)

Create a new page with stealth anti-detection configured.

Parameters:

ParamTypeDescription
browserBrowserPuppeteer browser instance

Returns: Promise<Page>


Scraper Functions

scrapeProfile(page, username)

Get a user's full profile data.

Parameters:

ParamTypeDescription
pagePagePuppeteer page
usernamestringX username (without @)

Returns: Promise<Profile>

const profile = await scrapeProfile(page, 'nichxbt');
// {
//   name: 'nich',
//   username: 'nichxbt',
//   bio: '...',
//   followers: 1234,
//   following: 567,
//   tweets: 890,
//   verified: false,
//   location: '...',
//   website: '...',
//   joinDate: '...',
//   avatar: 'https://...',
//   header: 'https://...'
// }

scrapeFollowers(page, username, options?)

Get a list of accounts that follow a user.

Parameters:

ParamTypeDefaultDescription
pagePagerequiredPuppeteer page
usernamestringrequiredX username
options.limitnumber1000Max followers to return

Returns: Promise<User[]>

const followers = await scrapeFollowers(page, 'nichxbt', { limit: 500 });
followers.forEach((f) => console.log(`@${f.username}: ${f.bio}`));

scrapeFollowing(page, username, options?)

Get a list of accounts a user follows.

Parameters: Same as scrapeFollowers

Returns: Promise<User[]>


scrapeTweets(page, username, options?)

Get a user's recent tweets.

Parameters:

ParamTypeDefaultDescription
pagePagerequiredPuppeteer page
usernamestringrequiredX username
options.limitnumber20Max tweets to return

Returns: Promise<Tweet[]>

const tweets = await scrapeTweets(page, 'nichxbt', { limit: 50 });
tweets.forEach(t => console.log(`${t.likes}❤️ ${t.text.slice(0, 80)}`));

searchTweets(page, query, options?)

Search for tweets matching a query.

Parameters:

ParamTypeDefaultDescription
pagePagerequiredPuppeteer page
querystringrequiredSearch query (supports X search operators)
options.limitnumber20Max results

Returns: Promise<Tweet[]>

// Basic search
const tweets = await searchTweets(page, 'xactions', { limit: 100 });

// Advanced search operators
const viral = await searchTweets(page, 'AI tools min_faves:1000 lang:en', { limit: 50 });

scrapeThread(page, tweetUrl)

Read a whole thread: the root post and every reply the author chained onto it.

Returns: Promise<ThreadTweet[]>

const thread = await scrapeThread(page, 'https://x.com/user/status/123456');
console.log(`${thread.length} tweets`);
console.log(thread.map((t) => t.text).join('\n\n'));

The HTTP client covers the same ground without a browser, and on the guest tier: new Scraper().getTweet(id), or scrapeFullThread from xactions/scrapers/twitter/http.


scrapeBookmarks(page, options?)

Export your saved bookmarks. Needs a logged-in page: bookmarks are private.

Returns: Promise<Bookmark[]>


scrapeMedia(page, username, options?)

Every image and video an account has posted, with the direct file URLs.

Returns: Promise<MediaItem[]>

const media = await scrapeMedia(page, 'nasa', { limit: 50 });
console.log(media[0].url);

Video specifically has a CLI route that needs no code: xactions media <user>, and the MCP tool x_download_video.


Other scrapers

scrapeLikes(page, username, opts), scrapeHashtag(page, hashtag, opts), scrapeListMembers(page, listId, opts), scrapeNotifications(page, opts), scrapeTrending(page), scrapeCommunityMembers(page, communityId, opts) and scrapeSpaces(page, opts) follow the same shape. See scrapers.md for the full table.


Manager Modules

Manager modules are higher-level Puppeteer-based automation tools.

Each one is an object of Puppeteer-driven functions plus a SELECTORS map.

import { dmManager, profileManager, postComposer } from 'xactions';

await postComposer.postThread(page, ['first tweet', 'second tweet']);
await dmManager.sendDM(page, 'nichxbt', 'hello');
ModuleWhat it does
articlePublisherPublish long-form Articles (Premium+)
bookmarkManagerSave, organize, export bookmarks
businessToolsBrand monitoring, competitor analysis
creatorStudioCreator dashboard and analytics
discoveryExploreTrending topics and the Explore page
dmManagersendDM, getConversations, exportConversation, getMessageRequests, updateDMSettings
engagementManagerLike, retweet, reply automation
grokIntegrationQuery Grok
notificationManagerRead and manage notifications
pollCreatorCreate polls
postComposerpostTweet, postThread, createPoll, schedulePost, quotePost, repost, deletePost
premiumManagerPremium subscription features
profileManagerUpdate bio, avatar, header, name, location, website
settingsManagerAccount settings and privacy
spacesManagerX Spaces

Signed webhooks

Any notification channel of type webhook POSTs JSON to a URL you control, and signs it so the receiver can prove it came from your install.

HeaderValue
X-XActions-Signaturesha256=<hex HMAC-SHA256 of the raw body>
X-XActions-TimestampUnix seconds when the request was signed
X-XActions-EventEvent type, for example follower_alert
X-XActions-DeliveryUUID, stable across retries of one delivery

Set XACTIONS_WEBHOOK_SECRET (or secret on the channel config) to turn on signing. Verify on the receiving side, against the raw body, not a reparsed object:

import { verifyWebhookSignature } from 'xactions';

const result = verifyWebhookSignature(rawBody, headers, process.env.XACTIONS_WEBHOOK_SECRET);
if (!result.valid) throw new Error(result.reason);

The comparison is constant-time, and a signature older than five minutes is rejected by default. signWebhookBody, deliverWebhook, listWebhookDeliveries and replayWebhookDelivery are exported from the package root too: a delivery is retried three times with backoff and every attempt is recorded in ~/.xactions/webhook-deliveries.json, so a failed one can be inspected and replayed. Full guide: notifications.md.


Package entry points

package.json publishes these subpaths and no others. A deep path into src/ throws ERR_PACKAGE_PATH_NOT_EXPORTED; from a clone, import the file by relative path instead.

ImportContents
xactionsEverything below re-exported, plus the manager modules and browserScripts
xactions/clientScraper, SearchMode, Tweet, Profile, error classes
xactions/scrapersPuppeteer scrapers, the adapter registry, scrape()
xactions/scrapers/twitterThe Puppeteer X scrapers on their own
xactions/scrapers/twitter/httpThe GraphQL HTTP layer: TwitterHttpClient, createAccountPool, createCheckpoint, every scrape* function
xactions/scrapers/bluesky, /mastodon, /threadsThe other platforms
xactions/streamingcreateStream, createLivePipeline, Topic
xactions/analyticsSentiment, monitors, reports, history
xactions/pluginsInstall, load and query plugins
xactions/portabilityExport, migrate, diff, X archive import
xactions/spacesjoinSpace, getSpaceTranscript
xactions/mcpThe MCP server, TOOLS, executeTool
xactions/cliThe CLI entry point

MCP Server

154 tools for AI agent integration. See MCP Server docs and mcp-setup.md.

npx xactions-mcp                        # stdio, what an MCP client spawns
npx xactions-mcp --http --port 8787     # Streamable HTTP on /mcp
npx xactions-mcp --list-groups          # every group and its tools

Claude Desktop config:

{
  "mcpServers": {
    "xactions": {
      "command": "npx",
      "args": ["-y", "xactions-mcp"],
      "env": {
        "XACTIONS_SESSION_COOKIE": "your_auth_token",
        "XACTIONS_CSRF_TOKEN": "your_ct0"
      }
    }
  }
}

Narrowing what an agent can reach

Tools are organised into groups: read, analytics, write, automation, monitoring, workflows, ai, data, graph, persona, dm, lists, spaces, grok, auth, drafts. --tools and --exclude accept tool names, group names, or prefix* patterns, and read XACTIONS_MCP_TOOLS / XACTIONS_MCP_EXCLUDE when the flags are absent.

npx xactions-mcp --tools read,analytics       # a research agent that cannot write
npx xactions-mcp --exclude write,automation   # everything except the account actions
npx xactions-mcp --tools 'x_get_*'            # only the getters

The draft-approval gate

--require-approval (or XACTIONS_MCP_REQUIRE_APPROVAL=1) makes every write tool return a draft id instead of acting. Nothing reaches X until a human runs xactions drafts approve <id> in a terminal. The drafts group (x_list_drafts, x_approve_draft, x_discard_draft, x_draft_status) stays available whatever --tools says, so an agent can always tell you what it is waiting on.

Daily action caps

Independently of approval mode, every write call is charged against a persistent per-account 24-hour budget in ~/.xactions/action-ledger.json, and a call that would go over is refused before it reaches X. Defaults follow X's published limits: 2,400 posts and replies, 500 likes, 500 reposts, 500 DMs, 400 follows and 400 unfollows per day. Override with XACTIONS_ACTION_CAPS (a JSON object) or ~/.xactions/action-caps.json. x_action_budget reports what is left.

HTTP transport

--http serves Streamable HTTP on /mcp instead of stdio, for agents that connect over a URL. Bind address defaults to 127.0.0.1 (--host / XACTIONS_MCP_HOST) and the port to 8787 (--port / PORT). Set XACTIONS_MCP_TOKEN and the server requires Authorization: Bearer <token> on every request. Never expose it without a token.


CLI Commands

npm install -g xactions
CommandDescription
xactions loginAuthenticate with X
xactions logoutClear saved credentials
xactions profile <user>Get profile data
xactions followers <user>List followers
xactions following <user>List following
xactions non-followers <user>Find non-followers
xactions tweets <user>Get tweets
xactions search <query>Search tweets
xactions hashtag <tag>Scrape hashtag
xactions thread <url>Unroll a thread
xactions media <user>Scrape media
xactions infoShow version and config

Common flags:

  • -l, --limit <n>: maximum items
  • -o, --output <file>: save to a file; the extension (.json, .csv, .xlsx) picks the format
  • --json: force JSON on stdout, ignoring --output

Global flags, before the command name:

  • --compact: one record per line, no colours or spinners, for pipes and agents
  • --fields <list>: with --compact, which fields to print
xactions --compact --fields id,likes,text tweets NASA --limit 5

Full list, flag by flag: cli-reference.md.


Browser Scripts

95 console scripts ship in scripts/, catalogued with their target page in browser-scripts.md (generated from each file's header by npm run docs:scripts).

A smaller registry of the copy-paste scripts under src/ is exported from the package, so a tool can list them without reading the repo:

import { browserScripts } from 'xactions';

console.log(Object.keys(browserScripts).length);   // 61
console.log(browserScripts.unfollowback);
// { file: 'src/unfollowback.js', description: "Unfollow users who don't follow you back" }

Types

TypeScript declarations ship with the package at types/index.d.ts, and package.json points types at them, so an editor picks them up with no @types install.

import type {
  ScrapedProfile,
  ScrapedTweet,
  ThreadTweet,
  User,
  MediaItem,
  Thread,
  VideoResult,
  Bookmark,
  Workflow,
  Stream,
  Plugin,
} from 'xactions';

The scraped shapes are ScrapedProfile and ScrapedTweet, not Profile and Tweet: Profile and Tweet are the runtime classes the HTTP client returns, exported from xactions/client. See types/index.d.ts for every interface.