Configuration

September 7, 2026 ยท View on GitHub

XActions is configured in four places, and which ones you need depends entirely on which surface you use.

SurfaceConfiguration needed
Browser console scriptsNone. Edit the CONFIG block in the script.
CLI~/.xactions/config.json, written by xactions login.
Node.js libraryCookies passed in code, or X_AUTH_TOKEN / X_CSRF_TOKEN.
MCP serverEnvironment block in your AI client's MCP config.
Self-hosted API and dashboard.env.

Only the last one needs a .env file at all. Copy .env.example if you are running the server.


Session cookies

Everything that reads private or session-tier data needs two cookies from a logged-in x.com session.

Where to get them: the CLI can read them for you, which is faster and less error-prone than copying by hand.

xactions login --from-browser firefox     # also: chrome, chromium, brave, edge, arc
xactions login --cookies-file cookies.txt # Netscape, Cookie-Editor JSON, Playwright storageState
xactions connect                          # log in through a real browser window

By hand: open x.com, log in, then DevTools (F12), Application, Cookies, https://x.com.

CookieWhat it isRequired
auth_tokenYour session. Treat it like a password.yes
ct0CSRF token X requires as a request header.yes

Both are required. With only auth_token, X still treats the request as logged out, and search, followers, likes, bookmarks, and DMs all answer 404. This is the single most common configuration mistake.

CLI

npx xactions login

Prompts for both and writes ~/.xactions/config.json:

{
  "authToken": "...",
  "csrfToken": "..."
}

xactions logout removes it.

Library and examples

export X_AUTH_TOKEN=...
export X_CSRF_TOKEN=...

or in code:

import { Scraper } from 'xactions/client';

const scraper = new Scraper();
await scraper.setCookies(`auth_token=${authToken}; ct0=${csrfToken}`);

A full cookie jar exported from the browser also works and is preferred when you have one, because it carries everything X expects:

await scraper.loadCookies('./cookies.json');   // [{ "name": "auth_token", "value": "..." }, ...]
await scraper.saveCookies('./cookies.json');   // persist a refreshed session

MCP server

Set them in the env block of your AI client's MCP configuration, not in a .env file. MCP servers are spawned with a minimal environment:

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

Without them the server still starts and still serves all 153 tools. The guest-tier ones (profiles, public timelines) work and the rest report that they need a session.

MCP server flags and env

VariableFlagEffect
XACTIONS_MCP_TOOLS--tools <list>Expose only these tools. Accepts tool names, group names, or prefix* patterns, comma separated.
XACTIONS_MCP_EXCLUDE--exclude <list>Hide these, same syntax.
XACTIONS_MCP_REQUIRE_APPROVAL--require-approvalHold every write call as a draft. Nothing reaches X until xactions drafts approve <id> runs in a terminal.
MCP_TRANSPORT=http--httpServe Streamable HTTP on /mcp instead of stdio.
XACTIONS_MCP_HOST--host <addr>HTTP bind address, default 127.0.0.1.
PORT--port <n>HTTP port, default 8787.
XACTIONS_MCP_TOKENnoneRequired bearer token for --http. Never expose the HTTP transport without one.
XACTIONS_ACTION_CAPSnoneJSON object overriding the daily per-action caps. Also readable from ~/.xactions/action-caps.json.
XACTIONS_ACCOUNTnoneNames which account the daily action ledger is charged against. Default default.

npx xactions-mcp --list-groups prints every group with its tools, which is the fastest way to build a --tools value. The groups are read, analytics, write, automation, monitoring, workflows, ai, data, graph, persona, dm, lists, spaces, grok, auth and drafts. The drafts group is always available regardless of --tools, so an agent can always report what it is waiting on.

Keeping them out of your repo

.env, .env.local, and cookies.json are gitignored. A leaked auth_token is a full account takeover, so:

  • Never paste one into an issue, a screenshot, or a PR.
  • Log out of x.com in that browser to invalidate a token you think leaked.
  • Prefer a secondary account for automation.

Environment variables

Only relevant when self-hosting the API server or dashboard. Grouped by what they turn on; nothing here is needed for the CLI, the library, or console scripts.

Server

VariableDefaultNotes
NODE_ENVdevelopmentproduction enables stricter startup checks.
PORT3001HTTP port.
API_URLhttp://localhost:3001Public base URL, used in generated links.
FRONTEND_URLhttp://localhost:3000Used for CORS and redirects.
CORS_ORIGINSlocalhost originsComma-separated allowlist.
LOG_LEVELinfodebug, info, warn, error.

Security

VariableNotes
JWT_SECRETRequired in production. The server refuses to start without it.
SESSION_SECRETRequired in production. Same.
ADMIN_API_KEYGuards the admin endpoints. Generate with openssl rand -hex 32.

Database and queue

VariableNotes
DATABASE_URLPostgreSQL connection string. See database.md.
REDIS_HOST / REDIS_PORT / REDIS_PASSWORDBacking store for the background job queue.

Scraping

VariableNotes
XACTIONS_SESSION_COOKIEauth_token value.
XACTIONS_CSRF_TOKENct0 value.
XACTIONS_MODElocal (Puppeteer, free) or remote (hosted API).
XACTIONS_API_URLEndpoint used in remote mode.
XACTIONS_SCRAPER_ADAPTERpuppeteer (default), playwright, or http.

Puppeteer

VariableNotes
PUPPETEER_HEADLESStrue in servers, false to watch a run.
PUPPETEER_NO_SANDBOXSet true in Docker and when running as root.
PUPPETEER_EXECUTABLE_PATHPoint at a system Chromium instead of the bundled one.

Optional integrations

VariableEnables
OPENROUTER_API_KEYLLM-backed sentiment mode and AI tweet generation.
TWITTER_CLIENT_ID / TWITTER_CLIENT_SECRETOAuth 2.0 login in the dashboard.
STRIPE_*Subscription billing.
X402_*Per-request payments on the hosted API. Documented inline in .env.example; verify a setup with npm run verify:x402.
SENTRY_DSNError reporting.

Personas and niches

The autonomous agent reads two JSON files that decide what it talks about and how it sounds. Both live in config/ and are plain data, so adding your own is a matter of dropping in a file.

Personas: how the agent writes

config/personas/ ships three: thought-leader, technical-builder, community-builder.

{
  "name": "ThoughtLeader",
  "tone": "opinionated, visionary, contrarian but well-reasoned",
  "expertise": ["technology trends", "industry analysis"],
  "opinions": ["The next decade belongs to builders, not fundraisers"],
  "avoid": ["empty platitudes", "engagement farming", "corporate jargon"],
  "exampleTweets": ["..."]
}

avoid matters more than it looks. It is the difference between output that reads like a person and output that reads like a bot, and it is the first field worth editing.

Niches: what the agent looks at

config/niches/ ships ai-engineering, saas-startups, and web3-crypto.

{
  "name": "AI Engineering",
  "searchTerms": ["AI agents", "LLM engineering", "prompt engineering"]
}

The agent uses these to find accounts and posts worth engaging with, which is also how it trains your timeline algorithm toward a topic.

Agent settings

Copy config/agent-config.example.json to data/agent-config.json and edit, or run the wizard:

npx xactions agent setup

Console script settings

Browser scripts are configured in the script itself. Every one opens with a CONFIG block:

const CONFIG = {
  maxUnfollows: Infinity,
  whitelist: [],
  dryRun: true,       // Preview without acting. Set false to run.
  delay: 2000,
};

Most destructive scripts ship with dryRun: true, but not all of them do, and the ones that do not act on the first run. Read the CONFIG block before you paste. See browser-scripts.md.