Operate

Use x402 and MPP

x402 and the Machine Payments Protocol (MPP) allow an agent to discover and pay for a service over HTTP. When the agent calls a paid endpoint, the service answers with HTTP 402 (Payment Required) and a payment challenge. The agent then pays the challenge in USDC and sends the request again. For example, if your agent calls Wren's Weather and the service answers with a 402 challenge, your agent will pay the challenge and repeat the call to get the response.

We record each of these payments as a send from an account that the agent's policy lets it send from. This means the same counterparty list, spending limits, and approvals apply to x402 and MPP payments as to any other send.

We support these payments in the CLI and the SDK. Note that the MCP connector does not support x402 or MPP payments.

Requirements

Before your agent can pay an endpoint, you'll need the following:

  • A wallet account on the challenge's network: That is Base where enabled, or Base Sepolia in the sandbox. Arc support is rolling out.
  • A send capability: The agent's policy must allow sends from that account.
  • A counterparty for the service: The address the service asks to be paid (payTo) must be saved as a counterparty wallet rail.

We do not add payTo as a counterparty for you, because a 402 response is untrusted. A service's address can change, so you may want to consider letting the agent request new counterparties.

Pay with the CLI

To pay an x402 endpoint, pass the URL and the wallet account the payment draws from:

Shell
catena x402 --url https://api.example.com/paid \
  --account acct_... --max-amount 0.25

For MPP, run catena mpp with the same options:

Option Use
--url The endpoint to call.
--account The wallet account the payment draws from.
--max-amount A price ceiling in USD. The CLI refuses a larger challenge before it pays anything. Pass it whenever you have one.
--data A request body. Implies POST and JSON.
--header "Name: Value" An extra request header.

In the example above, --max-amount 0.25 means the CLI will refuse any challenge over $0.25 before it pays anything. The CLI reference lists every option.

Read the result

The command prints JSON. When it needs something from you, it will exit non-zero, and the JSON names the problem. Below are the results that a run may return:

Field Meaning Next step
.paid The payment went through. .payment has the intent ID, amount, and recipient. .body has the response. Use the response.
.counterpartyNotFound The recipient is not a saved counterparty. Nothing was charged. Read .nameIsPlaceholder, create the counterparty with .createCommand, then run the same command again.
.approvalPending The payment is at or above the approval threshold. Ask an admin to approve it, then run the same command again. It uses the approval and pays once.
.networkMismatch The account cannot pay on the challenge's network. Nothing was charged. Run again with --account set to a wallet on one of .networkMismatch.requiredNetworks.

When .nameIsPlaceholder is false, the suggested name is the one the service gave for itself, and nobody has verified it, so confirm that you trust it before you create the counterparty. When it is true, you'll need to replace the placeholder name yourself.

If .bodyTruncated is true, you can read the full body from the file at .bodyPath.

Never fetch the URL again to see more

Important

Each run can pay again

Every run of the command can pay again, even for the same URL and price. Do not run the command a second time to read more of a response, and do not pay again after an unclear result.

To recover a payment that finished after an interruption, run catena intents get <id>. For x402, .data.paymentCredential.value holds the PAYMENT-SIGNATURE header that you can retry with while it is still valid.

Pay with the SDK

In the SDK, you wrap fetch so that it pays challenges from a wallet account:

TypeScript
import { createCatenaClient } from "@catena/sdk"
import { wrapFetchWithX402Payment } from "@catena/sdk/x402"

const client = createCatenaClient()
const fetchWithPayment = wrapFetchWithX402Payment(client, {
  accountId: walletAccountId,
  maxAtomicAmount: 1_000_000n, // 1 USDC
})

const response = await fetchWithPayment("https://api.example.com/paid-thing")

maxAtomicAmount is in atomic USDC units, so 1_000_000n is 1 USDC. Responses without a payable challenge pass through unchanged. For MPP, use wrapFetchWithMppPayment from @catena/sdk/mpp. See the SDK reference for details.

Evidence from sellers

Some x402 sellers sign their offer and receipt. When they do, Catena verifies both and keeps them on the intent (as evidence for an audit or a dispute). They do not change the payment.

Updated October 30, 2026 Applies to @catena/cli 0.19.0 and @catena/sdk 0.11.0 View as Markdown