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.jsonis the canonical machine-readable contract: every route, its input schema, its price underx-payment-info, andinfo.x-guidanceexplaining in prose how to use the API.GET /.well-known/x402lists 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
baseorsolana; CAIP-2 ids likeeip155:8453arrived 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 thePAYMENT-REQUIREDheader, 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.
Related
- ai-api.md - the full operation catalogue and prices
- mcp-setup.md - connecting Claude, Cursor and Codex
- extension.md - where account actions actually run
- x402.org - the protocol