x402 Payments from the CLI
Register a payout address, create and pay x402 USDC challenges (including the email-native flow), and inspect settlements and spend policy directly from the terminal with the primitive payments command group.
The primitive payments command group drives the same non-custodial x402 payment model as the SDKs, from your shell. Reach for it when you want to register a payout address, request or settle a USDC payment, or inspect settlements and spend policy without writing code, for example scripting a payment into CI, or testing a payment flow before wiring it into a Function.
One agent registers a payout address and requests a payment with charge; the paying agent signs locally with its own wallet key and settles with pay. The signing key never leaves your machine, it is read from an environment variable or flag and used only to sign locally. Networks are base (mainnet) and base-sepolia (testnet); amounts take either a human USDC value (--amount-usdc 0.01) or token base units (--amount 10000, since USDC has 6 decimals). Your org is resolved automatically from your API key, so payout registration takes no org flag.
This page assumes you already have the CLI installed and authenticated. See What is the Primitive CLI? and Authentication: login, signup, logout, whoami if you haven't set that up yet.
Set your wallet key#
The signing commands (register-payout-address, pay, pay-email, pay-email-step) need a wallet private key to sign locally. Set it as an environment variable so it never lands in shell history or the process list:
export PRIMITIVE_X402_PRIVATE_KEY=0x...
A --private-key flag exists as an escape hatch for scripted use, but the environment variable is preferred.
The non-signing commands (charge, get-challenge, list-payout-addresses, get-spend-policy, update-spend-policy) need only your Primitive API key (set via primitive login or PRIMITIVE_API_KEY); they don't touch the wallet key at all.
Register a payout address (payee, one time)#
Do this once per org before you ever call charge. It signs an ownership message locally with your wallet key and proves control of the address; the recovered address becomes your org's default payout destination for that network.
- 1
Export your wallet key#
export PRIMITIVE_X402_PRIVATE_KEY=0x... - 2
Register the address#
primitive payments register-payout-address --network base-sepolia --label treasuryThe command prints a human-readable summary by default. Pass
--jsonfor the raw registration object.
Your org id is resolved automatically from your API key, payout registration takes no --org flag.
Create a payment challenge (payee)#
charge creates an x402 payment challenge with a human USDC amount and prints the challenge JSON to stdout, with a one-line summary on stderr:
primitive payments charge --network base-sepolia --amount-usdc 0.01
Capture the challenge JSON to hand to the payer:
primitive payments charge --network base-sepolia --amount-usdc 0.01 > challenge.json
charge is the friendly verb that matches the SDKs' charge() / Charge() and accepts --amount-usdc. The lower-level create-challenge command takes base-unit --amount instead; either creates a challenge.
Pay a challenge (payer)#
pay signs and settles the challenge locally, using your wallet key. It reads the challenge inline, from a file, or piped on stdin:
primitive payments pay --challenge-file challenge.json
cat challenge.json | primitive payments pay
pay prints a human-readable summary by default, and raw JSON with --json.
pay needs PRIMITIVE_X402_PRIVATE_KEY (or --private-key) set to your wallet's private key. Anyone with that key can sign payments from your address, keep it out of shell history and CI logs.
Inspect a challenge or your payout addresses#
Look up a challenge by id, or list every payout address registered for your org:
primitive payments get-challenge --id <challenge-id>
primitive payments list-payout-addresses
Email-native payments#
The email-native x402 payment flow rides a real email thread instead of an out-of-band challenge id. The payee issues the challenge as an email; the payer signs it into an interaction.json payment step and sends it back.
- 1
Payee: issue the challenge over email#
create-email-challengetakes--amountin token base units only, unlikechargeit has no--amount-usdc. USDC has 6 decimals, so multiply by 1,000,000: 0.01 USDC is--amount 10000.primitive payments create-email-challenge --from payee@your-domain.example \ --to payer@their-domain.example --amount 10000 --network base-sepolia - 2
Payer: sign and send in one step#
pay-emailis the recommended payer path: it signs the challenge locally with your wallet key AND sends the signedinteraction.jsonattachment, so you skip the manual sign-then-send dance.--in-reply-tois the inbound challenge email you received; it's fetched to address the payment to the payee, withFromdefaulting to the address the challenge was sent to. The send is not threaded under the challenge itself, the payment associates byinteraction_idinstead.primitive payments pay-email --challenge-file challenge.json \ --in-reply-to <inbound-challenge-email-id> --waitThe message carries a short default note alongside the attachment; pass
--bodyto customize it.
For advanced cases where you need to deliver the signed envelope yourself (for example attaching it with primitive send --attachment), sign only without sending:
primitive payments pay-email-step --challenge-file challenge.json > interaction.json
All four signing commands (register-payout-address, pay, pay-email, pay-email-step) accept --json. pay-email and pay-email-step print JSON by default (the send result and the interaction.json bytes, respectively); --json switches them to a fuller envelope object.
Read and update the spend policy#
The spend policy guards outbound payments with a kill-switch, per-payment and daily caps in token base units, and a payee allowlist. Read the current policy, or update it, the update merges, so omitted fields keep their current value:
primitive payments get-spend-policy
primitive payments update-spend-policy --max-per-payment 5000000
For the full flag list of any command, run:
primitive payments <command> --help
Next steps#
Understand the non-custodial payment model, challenge lifecycle, and spend policy shared by every SDK and the CLI.
Handling Payment and Interaction Webhook EventsReceive payment.settled and interaction.x402.* webhook events when a challenge you created gets paid.
Charging and Registering Payout AddressesSee the equivalent register-payout-address and charge() flow from the Node.js SDK.
What is the Primitive CLI?Get oriented on the CLI's command surface and how it relates to the Node SDK and api-core.
Was this page helpful?