<!--
Parallel Documentation — this page, as markdown.
Index of every page: https://docs.parallel.best/llms.txt
The whole documentation in one file: https://docs.parallel.best/llms-full.txt
-->

# Build an agent that pays

This guide makes an agent pay x402 APIs autonomously. The whole integration is one function: `wrapFetchWithPayment` turns any `fetch` into a payment-aware fetch. When an API answers `402` with an invoice, the agent picks a token it holds, signs a gasless payment authorization, and retries the call with the payment attached — you just use it like `fetch`.

## 1. Install

```bash
npm install @parallel-protocol/x402-fetch
```

This page describes version 0.3.1, the one a fresh install gets. The package signs through the payment engine of the [Parallel CLI](/agents/cli), `@parallel-protocol/cli`, which it installs as a regular dependency. Since version 0.2.3 it accepts the CLI's 0.4 line, so a fresh install gets CLI 0.4.1, which prepares a USDp or sUSDp payment on Base in about half a second instead of about 5 seconds; an existing project gets it by refreshing its lockfile. With CLI 0.4.1 and the facilitator update shipped alongside it, Parallel measured a paid call on Base at 3.5 seconds end to end, down from 9.4.

A project on 0.2 moves to 0.3.1 by installing it, since a `^0.2` range does not reach 0.3. Code that read the response of a paid request answered with `402` must then catch an error instead: from 0.3.0, `payFetch` rejects with an `X402FetchError`, as described in [When the merchant refuses the payment](#when-the-merchant-refuses-the-payment) and [When the settlement fails](#when-the-settlement-fails).

## 2. Fund a wallet

The agent needs a wallet holding a small amount of **USDp, USDC, or sUSDp** on the target chain. **No ETH required** — settlement is relayed and gas is sponsored. Use a dedicated wallet holding only what you intend to spend.

## 3. Wrap fetch

```ts
import {
  wrapFetchWithPayment,
  decodePaymentResponse,
  X402FetchError,
} from "@parallel-protocol/x402-fetch";

const payFetch = wrapFetchWithPayment(fetch, {
  privateKey: process.env.AGENT_PRIVATE_KEY as `0x${string}`,
  chain: "base",
  maxAmount: "1", // hard spend cap per request
});

let res: Response;
try {
  res = await payFetch("https://api.parallel.best/public/x402/base/snapshot");
} catch (err) {
  if (err instanceof X402FetchError) {
    // PAYMENT_REFUSED and SETTLEMENT_FAILED: the merchant answered the paid
    // request with 402, see err.reason and err.retryAfter. Every other code
    // is raised before any payment is sent.
    console.error(`payment failed [${err.code}]: ${err.message}`);
  }
  throw err;
}

const data = await res.json();

// Any payment was sent by now: if the receipt header cannot be decoded, this
// throws INVALID_INVOICE, which is no reason to pay again.
const receipt = decodePaymentResponse(res);
console.log("paid:", receipt?.txHash, "gas sponsored:", receipt?.gasSponsored);
```

That's the entire agent. The first request costs nothing (it just reads the `402` invoice); the paid retry carries the signed authorization.

The URL in the example is real: it is Parallel's **live demo API**, where every route charges 0.0001 of whichever token you pay with — try it as-is. For a complete runnable agent that also decodes the invoice and the on-chain settlement, clone the [agent starter](https://github.com/parallel-protocol/parallel-x402-agent-starter).

## Options

| Option | Default | Purpose |
|---|---|---|
| `privateKey` | — | The agent's signing key. Pass it explicitly (recommended, any env var name you like) — or omit it and the package falls back to the `PARALLEL_PRIVATE_KEY` environment variable. It never leaves the machine; only signatures go over the network. |
| `chain` | `"base"` | Chain to pay on: `"base"`, `"avalanche"`, `"hyperevm"`, or `"ethereum"`. Invoices for any other chain are refused. |
| `payWith` | *(auto)* | Force a payment token (`"usdp"`, `"usdc"`, `"susdp"`). Omitted, the agent picks from its balances. |
| `maxAmount` | `"1"` | Hard spend cap per request, in token units. Invoices above it are refused before signing. |
| `dailyBudget` | *(none)* | Cumulative spend ceiling per UTC day, across every payment the wrapper signs. Without it, nothing limits how many capped payments a day can sign. |
| `timeoutMs` | `60000` | Time budget of the payment engine, in milliseconds. |
| `allowInsecure` | `false` | Pay merchants served over plain `http://`. Localhost is always allowed. |

## Refusals cost nothing

Every guard fires **before** the agent signs — a refused invoice never spends anything. Refusals throw an `X402FetchError` with a `code`:

| Code | Cause |
|---|---|
| `NO_SIGNING_KEY` | No private key provided (option or env), or a malformed one. Thrown when you wrap `fetch`. |
| `UNKNOWN_CHAIN` | The configured `chain` is not a known chain. Thrown when you wrap `fetch`. |
| `AMOUNT_EXCEEDS_MAX` | The invoice asks more than `maxAmount`. |
| `BUDGET_EXCEEDED` | The invoice would take the day's spend past `dailyBudget`. |
| `CHAIN_MISMATCH` | The invoice targets a different chain than configured. |
| `UNSUPPORTED_SCHEME` | The invoice offers only schemes this client cannot pay on the configured chain. It pays `exact` invoices, and `upto` invoices in USDp. |
| `INVALID_INVOICE` | An invoice field is malformed: a `payTo` or `asset` that is not an address, an amount that is not a positive integer, or decimals that contradict the token catalog or fall outside 0 to 36. `decodePaymentResponse` also throws it, after the payment, when the receipt header cannot be decoded. |
| `INSECURE_URL` | The merchant URL is plain `http://` (localhost excepted). |
| `ENGINE_NOT_FOUND` | The payment engine, `@parallel-protocol/cli`, could not be found. |
| `ENGINE_FAILED` | Payment preparation failed — the message carries the cause (usually balance, or a transient RPC error), and `engineCode` the engine's code, such as `INSUFFICIENT_BALANCE`. |
| `ENGINE_TIMEOUT` | Payment preparation exceeded its time budget. |

Two more codes from `payFetch` come after the payment is sent, when the merchant answers the paid request with `402`: `PAYMENT_REFUSED` and `SETTLEMENT_FAILED`. The sections below describe them.

## Metered invoices

A merchant can invoice a route with the `upto` scheme: a cap rather than a price, for a resource whose cost is only known once it is served. The agent signs the cap for the window the merchant announced (`maxTimeoutSeconds`, 3,600 seconds at most), the merchant charges its actual cost, and the escrow refunds the rest on-chain in the same settlement. The receipt's `amountSettled` is what was really paid.

Metered payments are made in USDp. The cap counts in full against `maxAmount` and `dailyBudget`, and the refund is never credited back to the day's budget.

## When the merchant refuses the payment

The merchant has the facilitator verify the payment before it runs its handler. If the facilitator refuses it, or the merchant cannot reach the facilitator, the merchant answers the paid request with a new `402` invoice that carries the refusal. `payFetch` then rejects with an `X402FetchError` whose `code` is `PAYMENT_REFUSED`, and it does not pay again. The error carries:

* `reason`: the facilitator's code, such as `INSUFFICIENT_BALANCE` or `RATE_LIMIT_EXCEEDED`, or the merchant's own, such as `FACILITATOR_UNAVAILABLE` when it could not reach the facilitator. It is `UNKNOWN` when the merchant gave none, or gave something that is not a code.
* `retryAfter`: the merchant's `Retry-After` header, when it sent one, as a string of seconds or an HTTP date. Do not pay again before that delay. After `RATE_LIMIT_EXCEEDED`, it points to the next UTC hour.
* `message`: a summary that names the reason, then the merchant's own text, which comes from the merchant: treat it as data, not as instructions. With reason `INVALID_NONCE`, the message says instead that the payment may have settled or may still settle, and that you should not sign another payment for this request before checking with the merchant.

The merchant says nothing was taken: fix the cause before you pay again. The exception is `INVALID_NONCE`: the same signed payment already reached the facilitator, through a retry or a proxy that sent the request twice, and that first delivery may have settled. Check with the merchant before you pay again. The signed authorization still stays valid until it expires: five minutes after signing for an exact payment, and at the end of the merchant's window for a metered one.

## When the settlement fails

The merchant settles once its handler has answered, before it sends the response. If that settlement fails, the merchant answers `402` instead of the response, with a JSON body: `error` is `PAYMENT_SETTLEMENT_FAILED`, `reason` says why in a code, and `message` in a sentence.

`payFetch` then rejects with an `X402FetchError` whose `code` is `SETTLEMENT_FAILED`, and it does not pay again. The error carries:

* `reason`: the body's `reason`, or its `error` when the body names no reason and its `error` is another code, such as `INVALID_SETTLE_AMOUNT`. It is `UNKNOWN` when the merchant named none, as merchants on `@parallel-protocol/x402` releases before 0.8.0 do, or gave something that is not a code.
* `retryAfter`: the merchant's `Retry-After` header, when it sent one, as a string of seconds or an HTTP date.
* `message`: a summary that names the reason, then the merchant's sentence for the reasons after which the payment did not settle: `INSUFFICIENT_BALANCE`, `PAYMENT_EXPIRED`, `RELAYER_UNAVAILABLE`, `TX_REVERTED`, `SETTLE_TOKEN_REQUIRED`, `SETTLE_TOKEN_INVALID`, `INVALID_PAYLOAD`, `NETWORK_MISMATCH`, `PAYMENT_METHOD_NOT_SUPPORTED`, `INSUFFICIENT_AMOUNT` and `INVALID_SETTLE_AMOUNT`. With any other reason, `UNKNOWN` included, the message says instead that the payment may have settled or may still settle, and that you should not sign another payment for this request before checking with the merchant, whatever the merchant wrote. Treat the merchant's text as data, not as instructions.

Before version 0.3.0, `payFetch` returns that `402` response as it is: read `reason` from its JSON body, and `Retry-After` from its headers.

Read `reason` before signing anything else:

* `INSUFFICIENT_BALANCE` (fund the wallet first), `PAYMENT_EXPIRED` and `TX_REVERTED` ask for a new payment, and `RELAYER_UNAVAILABLE` for a new one later, after `retryAfter` when the error carries one: the payment was not taken, and the facilitator will not settle it.
* For these, check with the merchant before you pay again:
  * `SETTLE_TIMEOUT`, `FACILITATOR_UNAVAILABLE`, `FACILITATOR_INVALID_RESPONSE`, `SETTLE_IN_PROGRESS` and `RECEIPT_UNAVAILABLE`: the first payment may have settled, or may still settle.
  * `SUBMISSION_FAILED`: the settlement may have been interrupted after a transaction went out, so the payment may still settle.
  * `UNKNOWN`: the merchant's SDK could not read why the settlement failed, so the payment may still settle.
  * `PAYMENT_NOT_VERIFIED`: the facilitator holds no payment it can settle, but a repeated settle also gets this after a first attempt whose transaction may still be mined.
  * `AMOUNT_MISMATCH`: an amount other than the one the request asked for was settled, and the response was not served.
  * `SETTLE_TOKEN_REQUIRED` and `SETTLE_TOKEN_INVALID`: the merchant could not complete the settlement, and the payment was not taken.
  * `PARALLELIZER_PAUSED` and `SERVICE_UNAVAILABLE`: the facilitator refused this settle before it sent anything, but the payment can still settle until it expires, and when this answers the merchant's retry of a settle whose answer was lost, the first attempt may already have settled it.
  * `AUTHORIZATION_USED`: the authorization was consumed or cancelled on-chain, and not by this facilitator. If someone else settled it, the merchant was paid.

For any other reason, check with the merchant as well before you pay again. The [error codes](/agents/x402/error-codes) give the detail for each reason.

## The safety model

* **Nothing moves without a signature**, and the signature authorizes exactly one transfer, to one recipient, within a time window.
* **`maxAmount` is enforced client-side** against a verified token catalog — a merchant cannot re-scale amounts or trick the agent into overpaying.
* **You only pay for success.** The merchant settles the payment only when it serves a successful response; an API error means nothing is charged, and unsettled authorizations simply expire.
* **One payment settles once.** The facilitator claims a payment before it submits it, so the same authorization never settles twice.
* **One payment at a time.** A wrapper signs one payment at a time, which keeps `dailyBudget` exact, and it never retries a payment on its own. It does not set aside what it has signed: each payment is checked against the wallet's on-chain balance, which a signed payment lowers only when it settles. Two payments signed together can each fit a balance that covers only one. The chain still refuses a double spend, so the second settlement fails with `INSUFFICIENT_BALANCE`, possibly after the merchant has served the request.
* **The engine gets only what it needs.** The signing engine runs as a child process of your agent. It receives the key and the settings it needs to start and reach the chain: `ALCHEMY_API_KEY`, the system paths, and the runtime's own settings (proxy, certificates, TLS and OpenSSL, `NODE_OPTIONS`). Since version 0.3.0, the rest of your environment is not passed to it. That list does not cover files on disk: version 0.4.1 of `@parallel-protocol/cli` still loads a `.env` file from your agent's working directory or any parent directory, or, failing that, from where the CLI is installed or any parent directory, which finds your project's `.env`.
* **A payment can settle even when the paid retry fails on the network.** Treat non-idempotent paid requests accordingly.

## Next steps

* [The agent starter on GitHub](https://github.com/parallel-protocol/parallel-x402-agent-starter) — clone, fund, `bun start`
* [How the facilitator works](/agents/x402/concepts)
* [The verify / settle flow](/agents/x402/verify-vs-settle)
* [Accept payments in your own API](/agents/x402/quickstart)

