Skip to main content
rail402 (package @rail402.dev/cli) is the buyer SDK on the command line. Every payment needs --max, passes the spending policy and the signing guard, and prints a receipt with the transaction hash and its explorer link.

From zero to a paid call on the testnet

Commands

Call input for pay, purchase and quote: --method, --query k=v (repeatable), --header "k: v", --data <json> or --data-file <path> for HTTP; --tool <name>, --args <json> or --args-file <path> and --transport streamable-http|sse for MCP; --path-param k=v for a listing’s path parameters. Payment flags: --asset, --scheme exact|upto (a preference; the policy decides), --session <name> and --idempotency-key <key>. Repeating a paid command with the same --idempotency-key never pays twice. It returns the stored result with replayed: true, or the stored error while the payment’s outcome is open. It checks that payment on chain first, and pays again only once the chain shows the earlier authorization expired unused. A key used again for another resource in the same window is refused with payment_request_invalid.

upto resources

quote, pay and purchase pay upto resources like exact ones. The seller’s price is a cap: the quote shows it, --max must cover it, and the receipt reports both what was authorized and what the seller settled:
The cap is reserved in the budget window when the authorization is signed; only the charge the chain proves stays spent (session show). tx <hash> recognises an upto settlement (x402.shape: "upto"). On testnet the canonical settlement contract is pinned by default; RAIL402_UPTO_CONTRACTS pins another (for a private network), RAIL402_UPTO_FACILITATORS limits the settlement authorities, and RAIL402_SCHEMES=exact turns upto off. See Spending policy for every check.

JSON output

With --json (or RAIL402_OUTPUT=json) stdout carries exactly one JSON document per command; progress, logs and --verbose payment events go to stderr.
result by command: Amounts are decimal strings in whole units of the asset ("0.015"); atomic fields are base units. JSON output escapes C1 control characters and bidirectional overrides, so seller text printed to a terminal is inert.

Exit statuses

A script can tell from the status alone whether retrying is safe. paid decides first: any error whose paid is settled or unknown exits 5, even when it is retryable. A paid command whose receipt status is unknown also exits 5, with an ok: true document. Status 3 also covers a signed payment that never reached the seller (payment_settlement_unconfirmed or egress_request_too_large with paid: none): nothing was paid, but its reserved budget stays pending until the authorization expires. Any failure after a payment was sent, including a budget ledger that cannot be written, reports paid: unknown. The error-code reference lists the exit status of every code.

Configuration

Settings are read from flags, then RAIL402_* environment variables, then the config file ($XDG_CONFIG_HOME/rail402/config.json, or --config <path>), then the network’s defaults. The variables are the same as the MCP server’s, plus RAIL402_FACILITATOR_URL (for tx attribution and seller check; defaults to the Bazaar URL), RAIL402_FRIENDBOT_URL (testnet only) and RAIL402_OUTPUT. The config file takes the same settings in camelCase (network, rpcUrl, bazaarUrl, maxPerCall, maxTotal, assets, keyFile, schemes, uptoContracts, …) and is refused if it holds anything that looks like a secret key. The key is found in this order: --key-file, RAIL402_SECRET_KEY_FILE, RAIL402_SECRET_KEY, the config file’s keyFile, then the default key file for the network ($XDG_CONFIG_HOME/rail402/keys/stellar-testnet.key). keys new and keys import never overwrite a file (key_exists). Global flags: --network stellar:testnet|stellar:pubnet, --rpc-url, --bazaar-url, --facilitator-url, --key-file, --config, --json, --verbose, --timeout <seconds> (default 120), and, for local development on testnet only, --allow-http and --allow-private-network. On stellar:pubnet the RPC and Bazaar URLs and both caps are required, account setup needs --yes and never uses Friendbot, and --usdc is refused with cli_command_not_supported_on_network.