docs
Get startedTroubleshootingPaymentsWebhooksAgent paymentsSwop MCPOviIdentity (swop.id)SwivyRPC (preview)API reference

Pay from an agent

Build an agent that buys from any Swop seller. Read the 402, sign a USDC authorization, retry, keep the receipt.

There are two ways for an agent to pay:

You are buildingUse
An agent with its own wallet (a server key, a hosted wallet)The x402 flow on this page. Any Swop product, any x402 server
An assistant acting for a Swop user from their Swop walletThe Swop MCP: swop_get_store to browse, then swop_pay_x402_link, which previews, waits for the user's yes and respects their caps

The rest of this page covers the first case.

Spend real money carefully

This moves real USDC on Base mainnet. Use a dedicated wallet holding only what the agent may spend, show your user the product, price and seller before paying, and set your own per-purchase and daily limits. Swop can't undo an on-chain payment.

What you need

  • A wallet on Base holding enough USDC for the purchase. The buyer pays no gas: the facilitator submits the transfer.
  • A library that can sign EIP-712 typed data: viem or ethers in Node, eth-account in Python.

Request the product and read the 402

const BUY_URL =
  'https://apps.apiswop.co/api/v5/x402/store/travis.swop.id/buy/6a7f63002e22feb6ad48dd7c';

const first = await fetch(BUY_URL, { headers: { Accept: 'application/json' } });
if (first.status !== 402) throw new Error(`Expected 402, got ${first.status}`);
const challenge = await first.json();

// Pick the offer you can pay: USDC on Base, exact scheme.
const req = challenge.accepts.find(
  (a) => a.scheme === 'exact' && a.network === 'eip155:8453',
);
const amount = req.amount ?? req.maxAmountRequired; // USDC base units (6 decimals)
console.log(`${challenge.resource.description}\nPrice: ${Number(amount) / 1e6} USDC to ${req.payTo}`);

Check the price against what your user approved before going further.

Sign a transferWithAuthorization

The signature authorizes exactly amount USDC to move from your wallet to payTo, valid for a short window.

Retry with the payment header

Encode the payment as base64 JSON and send it in PAYMENT-SIGNATURE (v1 servers use X-PAYMENT; Swop accepts both).

Keep the receipt

A 200 carries orderId and the on-chain transaction. Store both.

Complete example

buy.mjs
import { randomBytes } from 'node:crypto';
import { privateKeyToAccount } from 'viem/accounts';

const BUY_URL = process.argv[2];
const MAX_USDC = 5; // your own spending limit for this purchase
const account = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY);

// 1. Ask for the price.
const first = await fetch(BUY_URL, { headers: { Accept: 'application/json' } });
if (first.status !== 402) throw new Error(`Expected 402, got ${first.status}: ${await first.text()}`);
const challenge = await first.json();
const req = challenge.accepts.find((a) => a.scheme === 'exact' && a.network === 'eip155:8453');
if (!req) throw new Error('No USDC-on-Base offer');
const amount = BigInt(req.amount ?? req.maxAmountRequired);
if (amount > BigInt(MAX_USDC * 1e6)) throw new Error('Over budget');

// 2. Sign an EIP-3009 transferWithAuthorization for exactly that amount.
const now = Math.floor(Date.now() / 1000);
const authorization = {
  from: account.address,
  to: req.payTo,
  value: amount.toString(),
  validAfter: String(now - 60),
  validBefore: String(now + (req.maxTimeoutSeconds ?? 300)),
  nonce: `0x${randomBytes(32).toString('hex')}`,
};
const signature = await account.signTypedData({
  domain: {
    name: req.extra?.name ?? 'USD Coin',
    version: req.extra?.version ?? '2',
    chainId: 8453,
    verifyingContract: req.asset,
  },
  types: {
    TransferWithAuthorization: [
      { name: 'from', type: 'address' },
      { name: 'to', type: 'address' },
      { name: 'value', type: 'uint256' },
      { name: 'validAfter', type: 'uint256' },
      { name: 'validBefore', type: 'uint256' },
      { name: 'nonce', type: 'bytes32' },
    ],
  },
  primaryType: 'TransferWithAuthorization',
  message: {
    ...authorization,
    value: BigInt(authorization.value),
    validAfter: BigInt(authorization.validAfter),
    validBefore: BigInt(authorization.validBefore),
  },
});

// 3. Retry with the payment attached.
const payment = {
  x402Version: challenge.x402Version,
  scheme: req.scheme,
  network: req.network,
  payload: { signature, authorization },
};
const paid = await fetch(BUY_URL, {
  headers: {
    Accept: 'application/json',
    'PAYMENT-SIGNATURE': Buffer.from(JSON.stringify(payment)).toString('base64'),
  },
});

// 4. Keep the receipt.
const body = await paid.json();
if (paid.status !== 200) throw new Error(`Payment failed (${paid.status}): ${body.error}`);
console.log('Order', body.data.orderId, 'tx', body.data.receipt?.transaction);
AGENT_PRIVATE_KEY=0x… node buy.mjs "https://apps.apiswop.co/api/v5/x402/store/travis.swop.id/buy/6a7f63002e22feb6ad48dd7c"

Why the payment body is flat

The x402 v2 specification describes a payment that carries the chosen offer as accepted. Today's deployed facilitators still parse the flat shape { x402Version, scheme, network, payload }, so that's what Swop's own payer sends and what this example sends. Copy the network value exactly as the 402 stated it (eip155:8453).

Buying physical goods or items with options

Read variants and requiredOrderFields from the store listing, ask your user for the values, and send them as base64 JSON in an X-ORDER header on both requests:

const order = { Size: 'L', name: 'Ada Lovelace', email: 'ada@example.com', line1: '1 Market St', city: 'San Francisco', postalCode: '94105', country: 'US' };
const xOrder = Buffer.from(JSON.stringify(order)).toString('base64');
// add 'X-ORDER': xOrder to the headers of the first request and the paid retry

The payment then goes to escrow. After the seller ships, your agent can confirm receipt with a signature from the same wallet to release the funds.

Troubleshooting

ResponseLikely cause
402 with Payment verification failed: …Wrong amount, to, chain id or USDC contract in the signed message, an expired validBefore, or not enough USDC in the wallet
402 with Payment settlement failed: …The facilitator couldn't submit the transfer. Retry with a fresh nonce
400 with missingOptions or shipping details absent. Send X-ORDER
200 with duplicate: trueYou retried a payment that already settled. The original orderId is returned; nothing was charged twice

On this page