Function reference for the
xactionsnpm package.npm install xactions
- Two ways in
- HTTP client (no browser)
- Core Functions
- Scraper Functions
- Manager Modules
- Signed webhooks
- Package entry points
- MCP Server
- CLI Commands
- Browser Scripts
- Types
The package ships two independent scraping paths, and picking the wrong one is the most common reason a first script returns nothing.
| Path | Import | Browser | Login |
|---|---|---|---|
| HTTP client (recommended, what the CLI uses) | xactions/client |
none | not needed for profiles and public timelines |
| Puppeteer scrapers | xactions or xactions/scrapers |
Chromium | required, 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.
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.
Launch a Puppeteer browser with stealth mode enabled (avoids bot detection).
Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
options.headless |
boolean |
true |
Run in headless mode |
options.proxy |
string |
none | HTTP(S) proxy URL |
options.userDataDir |
string |
none | Persistent browser profile directory |
options.args |
string[] |
none | Additional 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' });Create a new page with stealth anti-detection configured.
Parameters:
| Param | Type | Description |
|---|---|---|
browser |
Browser |
Puppeteer browser instance |
Returns: Promise<Page>
Get a user's full profile data.
Parameters:
| Param | Type | Description |
|---|---|---|
page |
Page |
Puppeteer page |
username |
string |
X 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://...'
// }Get a list of accounts that follow a user.
Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
page |
Page |
required | Puppeteer page |
username |
string |
required | X username |
options.limit |
number |
1000 |
Max followers to return |
Returns: Promise<User[]>
const followers = await scrapeFollowers(page, 'nichxbt', { limit: 500 });
followers.forEach((f) => console.log(`@${f.username}: ${f.bio}`));Get a list of accounts a user follows.
Parameters: Same as scrapeFollowers
Returns: Promise<User[]>
Get a user's recent tweets.
Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
page |
Page |
required | Puppeteer page |
username |
string |
required | X username |
options.limit |
number |
20 |
Max 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)}`));Search for tweets matching a query.
Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
page |
Page |
required | Puppeteer page |
query |
string |
required | Search query (supports X search operators) |
options.limit |
number |
20 |
Max 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 });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.
Export your saved bookmarks. Needs a logged-in page: bookmarks are private.
Returns: Promise<Bookmark[]>
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.
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 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');| Module | What it does |
|---|---|
articlePublisher |
Publish long-form Articles (Premium+) |
bookmarkManager |
Save, organize, export bookmarks |
businessTools |
Brand monitoring, competitor analysis |
creatorStudio |
Creator dashboard and analytics |
discoveryExplore |
Trending topics and the Explore page |
dmManager |
sendDM, getConversations, exportConversation, getMessageRequests, updateDMSettings |
engagementManager |
Like, retweet, reply automation |
grokIntegration |
Query Grok |
notificationManager |
Read and manage notifications |
pollCreator |
Create polls |
postComposer |
postTweet, postThread, createPoll, schedulePost, quotePost, repost, deletePost |
premiumManager |
Premium subscription features |
profileManager |
Update bio, avatar, header, name, location, website |
settingsManager |
Account settings and privacy |
spacesManager |
X Spaces |
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.
| Header | Value |
|---|---|
X-XActions-Signature |
sha256=<hex HMAC-SHA256 of the raw body> |
X-XActions-Timestamp |
Unix seconds when the request was signed |
X-XActions-Event |
Event type, for example follower_alert |
X-XActions-Delivery |
UUID, 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.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.
| Import | Contents |
|---|---|
xactions |
Everything below re-exported, plus the manager modules and browserScripts |
xactions/client |
Scraper, SearchMode, Tweet, Profile, error classes |
xactions/scrapers |
Puppeteer scrapers, the adapter registry, scrape() |
xactions/scrapers/twitter |
The Puppeteer X scrapers on their own |
xactions/scrapers/twitter/http |
The GraphQL HTTP layer: TwitterHttpClient, createAccountPool, createCheckpoint, every scrape* function |
xactions/scrapers/bluesky, /mastodon, /threads |
The other platforms |
xactions/streaming |
createStream, createLivePipeline, Topic |
xactions/analytics |
Sentiment, monitors, reports, history |
xactions/plugins |
Install, load and query plugins |
xactions/portability |
Export, migrate, diff, X archive import |
xactions/spaces |
joinSpace, getSpaceTranscript |
xactions/mcp |
The MCP server, TOOLS, executeTool |
xactions/cli |
The CLI entry point |
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 toolsClaude Desktop config:
{
"mcpServers": {
"xactions": {
"command": "npx",
"args": ["-y", "xactions-mcp"],
"env": {
"XACTIONS_SESSION_COOKIE": "your_auth_token",
"XACTIONS_CSRF_TOKEN": "your_ct0"
}
}
}
}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--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.
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 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.
npm install -g xactions| Command | Description |
|---|---|
xactions login |
Authenticate with X |
xactions logout |
Clear 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 info |
Show 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 5Full list, flag by flag: cli-reference.md.
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" }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.