⚡ XActions
📖 Guides & Reference

Paying for XActions: x402 and MCP

XActions sells public X (Twitter) data by the call. There is no account, no API key, no plan and no rate-limit tier. A client that wants data pays for that one request in USDC and gets it. A client that does not pay gets a machine-readable price list back instead of a login page.

This document is the whole contract: what is for sale, how a caller pays, and how identity works when nobody signs up.

What is live

Endpoint Price Returns
GET or POST /api/ai/scrape/profile $0.001 One account's public profile: display name, bio with t.co links expanded, location, website, join date, follower and following counts, post and like counts, avatar, banner, verification, pinned post id
GET or POST /api/ai/scrape/tweets $0.005 That account's recent posts with engagement counts, media (direct mp4 URLs for video), quoted posts, and a pagination cursor

Free, no payment and no key: POST /api/video/extract, GET /api/video/download, POST /api/ask, GET /api/health, GET /api/ai/pricing, GET /openapi.json, GET /.well-known/x402.

Both chains are offered on every paid call, Solana first:

Chain Network id Asset
Solana mainnet solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp USDC EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
Base mainnet eip155:8453 USDC 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913

Solana leads because the economics only work there: sub-second finality and fees around $0.00025 mean a $0.001 call is a real payment rather than a rounding error against gas. Base is listed alongside it because that is what most agent tooling reaches for first.

How a caller pays

Ask for the resource with no payment. The answer is 402 with the terms.

curl -s "https://xactions.app/api/ai/scrape/profile?username=nasa"
{
  "x402Version": 1,
  "error": "Payment required",
  "resource": {
    "url": "https://xactions.app/api/ai/scrape/profile",
    "method": "GET",
    "mimeType": "application/json"
  },
  "accepts": [
    {
      "scheme": "exact",
      "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
      "amount": "1000",
      "maxAmountRequired": "1000",
      "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
      "payTo": "2DdJ6AxTpdaSsZcbJ9AJV113oBNPBP5WM8L4HTBFnFv6",
      "maxTimeoutSeconds": 300
    },
    { "scheme": "exact", "network": "eip155:8453", "amount": "1000", "...": "..." }
  ]
}

amount is in the token's smallest unit. USDC has 6 decimals on both chains, so 1000 is $0.001.

Pick an entry, sign a transfer of exactly that amount to payTo with your own wallet, and send the identical request again with the signed payload base64 in X-PAYMENT. The server verifies it with a facilitator, runs the work, settles the payment, and returns the result with a receipt in X-PAYMENT-RESPONSE.

Any x402 client library does this for you:

import { withPaymentInterceptor } from 'x402-axios';
import axios from 'axios';
import { privateKeyToAccount } from 'viem/accounts';

const api = withPaymentInterceptor(
  axios.create({ baseURL: 'https://xactions.app' }),
  privateKeyToAccount(process.env.PRIVATE_KEY),
);

const { data } = await api.get('/api/ai/scrape/profile?username=nasa');
console.log(data.profile.followers);

Both protocol versions work

Two generations of x402 are in the wild and XActions speaks both, so an older client and a current one behave identically:

v1 v2
Challenge JSON body with x402Version and accepts base64 PAYMENT-REQUIRED header
Payment X-PAYMENT PAYMENT-SIGNATURE
Receipt X-PAYMENT-RESPONSE PAYMENT-RESPONSE

Every 402 carries the body and the header. A payment is read from whichever header the client sent, and the receipt goes back under both names.

Settlement happens after the work

The order is verify, then run the handler, then settle. If the scrape fails, the payment is never broadcast: a caller is not charged for a response they did not get, and a receipt in hand always corresponds to a result in hand.

How authentication works

There is none, and that is the design. The payment is the credential.

A traditional API answers "who are you?" with an API key: the server issues a secret, the client stores it, both sides rotate it, and the server keeps an account to hang quotas off. Every one of those steps assumes a human signed up. An agent cannot sign up, cannot pass a CAPTCHA, cannot read the email confirmation, and cannot agree to terms on someone's behalf.

x402 removes the question. The client proves it will pay, and the proof is a signature over a stablecoin transfer. The server learns exactly one thing about the caller: the address the money came from, which arrives as payer in the facilitator's verification response and is echoed back as paidBy. That address is a durable identity if the caller reuses it, and a fresh one every call if they do not. Either is fine, because nothing is granted on the basis of identity. Access is granted on the basis of payment.

What this buys, in practice:

  • No key to leak. There is no long-lived secret in the agent's environment. The worst case for a stolen payment payload is that a single request worth a tenth of a cent gets replayed, and facilitators reject replays anyway.
  • No sign-up. An agent that has never seen XActions can discover it, read the price, pay, and get data inside one conversation turn.
  • No tier negotiation. Volume is priced by usage automatically, because usage is the payment.
  • Revenue without ads. The reason this matters commercially: agent traffic has no human eyeballs to monetise, so an API either charges for itself or is dead weight.

If a future feature does need durable identity (a saved workspace, a webhook subscription), the address that paid is the natural key to hang it on, proven by a wallet signature rather than a password. That is the SIWX pattern ("sign in with X-chain"): the client signs a challenge string with the same wallet it pays with, and the server accepts the recovered address as the account. No password, no email, no reset flow.

How this works over MCP

MCP is the transport agents actually use, and it has two shapes here.

Local, over stdio

npx xactions-mcp runs the server on your own machine, talking stdio to Claude Desktop, Cursor or Codex. This path is free and unauthenticated by design: the process runs as you, on your machine, against your own logged-in browser session. There is nobody to authenticate to. See mcp-setup.md.

Remote, over Streamable HTTP: live at https://xactions.app/mcp

A hosted MCP server is a different animal: it is a public endpoint, so it needs both identity and a way to charge. XActions handles them separately.

Point any MCP client at that URL and it works on the first try. There is nothing to sign up for.

{ "mcpServers": { "xactions": { "url": "https://xactions.app/mcp" } } }
Tool Price Returns
xactions_docs free Answers about XActions from its own documentation
x_video free Every mp4 variant for a post that has video
x_profile $0.001 One account's public profile
x_post $0.001 One post with its engagement metrics
x_posts $0.005 An account's recent posts
x_thread $0.005 A thread, unrolled

tools/list carries the price in _meta["x402/price"], so an agent budgets before it calls rather than discovering the cost by being refused.

Payment: x402 per tool call. @xactions/x402-mcp prices individual tools and gates them exactly the way the HTTP API is gated. That package is deliberately generic: it knows nothing about X, and it is published so any MCP server can charge for its tools in three lines.

const gate = createToolPaymentGate({ payTo, prices: { search: '$0.01' } });

const checked = await gate.check({ message, request });
if (checked.response) return checked.response;     // unpaid: the terms
const response = await yourServer.handle(message, checked.context);
return gate.finalize(response, checked);           // paid: settle, attach receipt

A tools/call for a priced tool with no payment comes back as a tool result with isError: true whose structuredContent is the same PaymentRequired document the HTTP 402 carries. The client signs, then retries the call with the payload in _meta["x402/payment"]. The settlement receipt returns in _meta["x402/payment-response"]. Free tools (tools/list, x_login, x_get_settings) pass straight through, so an agent can always enumerate what is available before deciding to spend anything.

This is the x402 MCP transport binding, not a private invention, so any client that speaks it works unmodified.

Identity: OAuth 2.1, when a session is needed. The MCP authorization spec makes a remote server an OAuth 2.1 resource server: it publishes Protected Resource Metadata (RFC 9728), tokens are bound to it with resource indicators (RFC 8707), and PKCE is mandatory. That machinery exists for tools that act on a user's account. XActions deliberately keeps those tools out of the hosted service entirely: follow, unfollow, like, reply, post and DM run in the browser extension, in the caller's own logged-in session, and the hosted API answers 501 pointing there. We never hold X credentials, so there is no session to federate and no token to steal.

The result is a clean split:

Capability Where it runs How it authenticates
Public reads (profile, posts, video, search) Hosted, at the edge x402 payment, no identity
Account actions (follow, like, post, DM) Your browser, via the extension Your existing X session, never leaves your machine
Local automation (CLI, stdio MCP) Your machine Your own cookies, nothing sent anywhere

The facilitator

A facilitator is the piece that checks a payment payload and broadcasts it. The server never touches a chain directly.

XActions defaults to PayAI's (https://facilitator.payai.network). It needs no key and settles Base and Solana mainnet, which matters more than it sounds: the reference facilitator at x402.org/facilitator is testnet only. Point a production server at that one and it will happily publish mainnet terms that no caller can ever pay, and the failure only surfaces when someone tries to spend real money.

That class of mistake is now impossible to ship silently. On every 402 the offered chains are intersected with the facilitator's own /supported list before they are published, and GET /api/ai/health reports the intersection:

{
  "x402": {
    "facilitator": "https://facilitator.payai.network",
    "facilitatorReachable": true,
    "networks": [
      { "network": "solana:5eykt...", "name": "solana", "settleable": true },
      { "network": "eip155:8453", "name": "base", "settleable": true }
    ],
    "unsettleable": []
  }
}

If the facilitator cannot be reached the configured chains are offered unchanged: a transient outage should not quietly turn a paid API into a free one.

Swap facilitators with X402_FACILITATOR_URL. Coinbase's CDP facilitator is the other obvious choice: it settles mainnet too, and using it opts a service into the x402 Bazaar, which is what feeds agentic.market. It needs a CDP API key and secret (free tier, 1,000 transactions a month).

Discovery

Agents find all of this without being told:

  • GET /openapi.json is the canonical machine-readable contract: every route, its input schema, its price under x-payment-info, and info.x-guidance explaining in prose how to use the API.
  • GET /.well-known/x402 lists the paid resources with full payment terms and input/output schemas.

XActions is registered on x402scan, which probes the spec and the live 402 behaviour before it will list anything. Two details were what made the difference, and they are worth knowing if you list your own:

  • Answer HEAD. A crawler probes with it before anything else. On Cloudflare Pages an unclaimed method falls through to the catch-all function, and a 503 there reads as "this endpoint is not really live".
  • Use v1 network names in the v1 body. v1 identifies a chain as base or solana; CAIP-2 ids like eip155:8453 arrived with v2. A v1 indexer handed a CAIP-2 id rejects the whole challenge as unparseable. XActions sends v1 names in the JSON body and CAIP-2 in the PAYMENT-REQUIRED header, so both generations read the same terms.

Both documents describe only what this deployment actually serves. XActions can run several hundred more operations when self-hosted with Postgres and a browser pool, and those are deliberately absent from the deployed spec: advertising an endpoint that would answer 503 takes an agent's money for nothing and poisons the listing.

Running your own

Nothing here is specific to xactions.app. Set two variables and any deployment takes payments to its own wallets:

X402_PAY_TO_ADDRESS=0x...                # Base and every other EVM chain
X402_PAY_TO_ADDRESS_SOLANA=...           # Solana
X402_NETWORK=eip155:8453                 # set explicitly, see below
X402_SOLANA_NETWORK=solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp
X402_FACILITATOR_URL=https://facilitator.payai.network   # optional, this is the default

Either address alone is enough; the 402 offers whichever chains are configured.

Generate the receiving keypairs with:

node scripts/create-x402-wallets.mjs

It mints ed25519 for Solana and secp256k1 for EVM locally, writes the private keys to a gitignored 0600 file, and prints only the addresses. The keys are needed to move funds out; they never have to be deployed anywhere.

Set the network variables explicitly. They default from NODE_ENV, and bundlers substitute a literal for process.env.NODE_ENV at build time, so an edge deployment can silently inherit the build machine's idea of the environment and publish testnet terms on a production site. XActions reads process.env['NODE_ENV'] to dodge that substitution, but naming the networks outright removes the question.

⚡ Free and open source

No API keys, no monthly fees, no signup. Star the repo if it saved you a subscription.

View on GitHub