> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blockradar.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Make Agent Payments

> Let AI agents pay for APIs in stablecoins from a Blockradar wallet, without the private key leaving Blockradar

<Note>
  In a nutshell<br />
  AI agents can pay for APIs and data per request, in stablecoins, through open agent payment protocols. With Blockradar, an agent pays from a wallet whose private key never leaves Blockradar, using the [Typed Data Signing](/en/essentials/signing#typed-data-signing-evm-only) endpoint you already have.
</Note>

## Supported Protocols

| Protocol | Networks and tokens |
| - | - |
| [x402](https://www.x402.org) | EVM chains, USDC (EIP-3009) |

<Tip>
  Selling to agents instead? See [Accept Agent Payments](/en/use-cases/accept-agent-payments).
</Tip>

## Prerequisites

<Steps>
  <Step title="API Key">
    Get your API key from the [Blockradar Dashboard](https://dashboard.blockradar.co). Navigate to **Developers** to generate one.
  </Step>

  <Step title="EVM Master Wallet">
    Create a master wallet in the dashboard on the chain the API charges on, such as Base (see [Create a Master Wallet](/en/guides/create-master-wallet)). Agent payments through Blockradar are EVM only.
  </Step>

  <Step title="USDC Enabled">
    Enable USDC on the wallet so balances and deposits are tracked. See [Asset Management](/en/use-cases/asset-management).
  </Step>
</Steps>

## Give the Agent Its Own Budget

<Warning>
  Signing has no per-payment spending limit<br />
  Anyone holding an API key with access to a wallet can sign a payment of any amount from that wallet or its addresses. Blockradar does not cap what an agent signs. Limit the damage a misbehaving agent or a leaked key can do by giving the agent a dedicated address that holds only what it is allowed to spend.
</Warning>

A child address with auto-sweep turned off works well as an agent budget. Top it up with the amount the agent may spend, and the agent can never pay more than that balance.

<CodeGroup>
  ```bash Curl theme={null}
  curl --request POST \
    --url https://api.blockradar.co/v1/wallets/{walletId}/addresses \
    --header 'Content-Type: application/json' \
    --header 'x-api-key: <api-key>' \
    --data '{
      "name": "Research agent budget",
      "disableAutoSweep": true,
      "metadata": {
        "purpose": "agent-payments"
      }
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    `https://api.blockradar.co/v1/wallets/${walletId}/addresses`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': apiKey
      },
      body: JSON.stringify({
        name: 'Research agent budget',
        disableAutoSweep: true,
        metadata: { purpose: 'agent-payments' }
      })
    }
  ).then(r => r.json());

  console.log('Agent address:', response.data.address);
  console.log('Address ID:', response.data.id);
  ```
</CodeGroup>

<Note>
  Keep `disableAutoSweep: true` on an agent's address. With auto-sweep on, the USDC you send to fund the agent is swept to the master wallet, and the agent's payments fail for lack of funds.
</Note>

Paying from the master wallet also works. Use the master wallet endpoints in the examples below and leave out `addressId`. The master wallet's whole balance is then within the agent's reach, so do this only with a master wallet dedicated to the agent.

On the [Checkout plan](/en/use-cases/checkout#checkout-plan), child address operations are not available, so use a master wallet dedicated to the agent.

***

## How x402 Works

[x402](https://www.x402.org) is an open protocol that lets an API charge per request: it answers with HTTP `402 Payment Required`, and the caller retries with a signed USDC payment. x402 has three parties: the **buyer** (your agent), the **seller** (the API being paid), and a **facilitator** that checks the payment and submits it on-chain.

<Steps>
  <Step title="The API asks for payment">
    The agent calls a paid endpoint. The API responds with `402 Payment Required` and a `PAYMENT-REQUIRED` header listing what it accepts: network, token, amount, and the `payTo` address.
  </Step>

  <Step title="Blockradar signs the payment">
    The agent turns one of those options into an [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) `TransferWithAuthorization` and signs it with Blockradar's typed data endpoint. Nothing is sent on-chain yet.
  </Step>

  <Step title="The agent retries with the signature">
    The agent repeats the request with the signed payment in the `PAYMENT-SIGNATURE` header.
  </Step>

  <Step title="The facilitator settles">
    The seller's facilitator verifies the signature and submits the transfer on-chain, paying the gas itself. The API returns the response, with the settlement result in a `PAYMENT-RESPONSE` header.
  </Step>
</Steps>

The agent's wallet needs **USDC but no gas**. The signature authorizes one transfer of an exact amount to an exact address, and the facilitator cannot change either.

***

## Pay with x402

Fund the agent's address first, as described in [Give the Agent Its Own Budget](#give-the-agent-its-own-budget).

### Option 1: Use the x402 client SDK

The official [`@x402/fetch`](https://www.npmjs.com/package/@x402/fetch) client handles the 402 exchange for you. It only needs a signer with an `address` and a `signTypedData` method. The adapter below implements that signer on Blockradar's typed data endpoint, so the key stays in Blockradar.

```bash theme={null}
npm install @x402/fetch @x402/evm
```

```javascript JavaScript theme={null}
import { wrapFetchWithPaymentFromConfig, decodePaymentResponseHeader } from '@x402/fetch';
import { ExactEvmScheme } from '@x402/evm';

// An x402 signer whose key stays in Blockradar. Pass addressId to pay from a child address.
function blockradarSigner({ apiKey, walletId, addressId, address }) {
  const path = addressId
    ? `/wallets/${walletId}/addresses/${addressId}/signing/typed-data`
    : `/wallets/${walletId}/signing/typed-data`;

  return {
    address,
    async signTypedData({ domain, types, message }) {
      // Blockradar derives the domain type from `domain`, so EIP712Domain must not be in `types`
      const { EIP712Domain, ...signTypes } = types;

      const res = await fetch(`https://api.blockradar.co/v1${path}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey },
        // x402 passes uint256 values as BigInt, which JSON.stringify cannot serialize
        body: JSON.stringify({ domain, types: signTypes, message }, (_, v) =>
          typeof v === 'bigint' ? v.toString() : v
        ),
      });
      const body = await res.json();
      if (!res.ok) {
        throw new Error(`Blockradar signing failed: ${JSON.stringify(body.message)}`);
      }
      if (body.data.senderAddress.toLowerCase() !== address.toLowerCase()) {
        throw new Error(`Signed by ${body.data.senderAddress}, expected ${address}`);
      }
      return body.data.signedTransaction.signature;
    },
  };
}

const signer = blockradarSigner({
  apiKey: process.env.BLOCKRADAR_API_KEY,
  walletId: process.env.BLOCKRADAR_WALLET_ID,
  addressId: process.env.BLOCKRADAR_ADDRESS_ID, // omit to pay from the master wallet
  address: process.env.AGENT_ADDRESS,           // the address that holds the USDC
});

const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, {
  schemes: [{ network: 'eip155:8453', client: new ExactEvmScheme(signer) }], // Base mainnet
});

const response = await fetchWithPayment('https://api.example.com/premium-data');
console.log(await response.json());

const settlement = response.headers.get('PAYMENT-RESPONSE');
if (settlement) {
  console.log(decodePaymentResponseHeader(settlement)); // { success, transaction, network, payer }
}
```

Register one scheme per network the agent pays on (`eip155:84532` for Base Sepolia while testing), with a wallet on that chain behind each.

### Option 2: Build the payment yourself

Without the SDK, or in another language, a payment takes three HTTP calls: the first request, the signing call to Blockradar, and the paid retry. The steps below pay 0.01 USDC on Base.

#### Step 1: Read the payment requirements

Call the API. A `402` response carries a base64-encoded `PAYMENT-REQUIRED` header. Decoded, it looks like this:

```json theme={null}
{
  "x402Version": 2,
  "error": "PAYMENT-SIGNATURE header is required",
  "resource": {
    "url": "https://api.example.com/premium-data",
    "description": "Access to premium market data",
    "mimeType": "application/json"
  },
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:8453",
      "amount": "10000",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "payTo": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
      "maxTimeoutSeconds": 60,
      "extra": {
        "name": "USD Coin",
        "version": "2"
      }
    }
  ]
}
```

Choose an entry in `accepts` that your wallet can pay:

* `scheme` is `exact`.
* `network` is the wallet's chain. `eip155:8453` is Base mainnet.
* `extra.assetTransferMethod` is absent or `eip3009`. The `permit2` method needs an on-chain token approval first, which costs gas, so this guide does not cover it.

`amount` is in the token's smallest unit. USDC has 6 decimals, so `10000` is 0.01 USDC.

#### Step 2: Sign the authorization

Build the `TransferWithAuthorization` from the requirements and sign it from the agent's address:

| Field | Value |
| - | - |
| `domain.name`, `domain.version` | `extra.name` and `extra.version` |
| `domain.chainId` | The number after `eip155:` in `network`, as a JSON number |
| `domain.verifyingContract` | `asset`, the USDC contract |
| `message.from` | The agent's address |
| `message.to` | `payTo` |
| `message.value` | `amount` |
| `message.validAfter` | A Unix time slightly in the past, such as now minus 600 seconds |
| `message.validBefore` | Now plus `maxTimeoutSeconds` |
| `message.nonce` | 32 random bytes as hex. Generate a new one for every payment |

<CodeGroup>
  ```bash Curl theme={null}
  curl --request POST \
    --url https://api.blockradar.co/v1/wallets/{walletId}/addresses/{addressId}/signing/typed-data \
    --header 'Content-Type: application/json' \
    --header 'x-api-key: <api-key>' \
    --data '{
      "domain": {
        "name": "USD Coin",
        "version": "2",
        "chainId": 8453,
        "verifyingContract": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
      },
      "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" }
        ]
      },
      "message": {
        "from": "0x947514e4B803e312C312da0F1B41fEDdbe15ae7a",
        "to": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
        "value": "10000",
        "validAfter": "1791106604",
        "validBefore": "1791107264",
        "nonce": "0x312a453ed3d129ba71de863aa623245c0e5e120bad410d995cea6739aa8ee98b"
      }
    }'
  ```

  ```javascript JavaScript theme={null}
  import { randomBytes } from 'node:crypto';

  const now = Math.floor(Date.now() / 1000);
  const authorization = {
    from: agentAddress,
    to: accepted.payTo,
    value: accepted.amount,
    validAfter: String(now - 600),
    validBefore: String(now + accepted.maxTimeoutSeconds),
    nonce: '0x' + randomBytes(32).toString('hex'),
  };

  const res = await fetch(
    `https://api.blockradar.co/v1/wallets/${walletId}/addresses/${addressId}/signing/typed-data`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey },
      body: JSON.stringify({
        domain: {
          name: accepted.extra.name,
          version: accepted.extra.version,
          chainId: Number(accepted.network.split(':')[1]),
          verifyingContract: accepted.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' },
          ],
        },
        message: authorization,
      }),
    }
  );
  const signed = await res.json();
  if (!res.ok) throw new Error(`Signing failed: ${JSON.stringify(signed.message)}`);

  const signature = signed.data.signedTransaction.signature;
  ```
</CodeGroup>

To sign from the master wallet instead, use `POST /v1/wallets/{walletId}/signing/typed-data` and set `from` to the master wallet's address. The response is the standard [typed data response](/en/essentials/signing#typed-data-response); the signature is in `data.signedTransaction.signature`.

#### Step 3: Retry with the payment

Wrap the signature and authorization in a payment payload, base64-encode it, and send it in the `PAYMENT-SIGNATURE` header on the same request:

```javascript JavaScript theme={null}
const paymentPayload = {
  x402Version: 2,
  resource: paymentRequired.resource,
  accepted,                              // the entry you chose from `accepts`
  payload: { signature, authorization },
};

const paid = await fetch('https://api.example.com/premium-data', {
  headers: {
    'PAYMENT-SIGNATURE': Buffer.from(JSON.stringify(paymentPayload)).toString('base64'),
  },
});
```

#### Step 4: Check the settlement

A successful response includes a base64-encoded `PAYMENT-RESPONSE` header:

```json theme={null}
{
  "success": true,
  "transaction": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
  "network": "eip155:8453",
  "payer": "0x947514e4B803e312C312da0F1B41fEDdbe15ae7a"
}
```

`transaction` is the on-chain hash of the USDC transfer. If the payment fails, the API answers `402` again, and `PAYMENT-RESPONSE` carries an `errorReason` such as `insufficient_funds`.

<Accordion title="Calling an x402 v1 API">
  Servers on x402 version 1 differ from the flow above in four ways:

  * The payment requirements are in the `402` response **body**, not a header.
  * Networks are named (`base`, `base-sepolia`) rather than `eip155:<chainId>`. Base is chain ID `8453`; Base Sepolia is `84532`.
  * The amount field is `maxAmountRequired`, not `amount`.
  * The payment goes in the `X-PAYMENT` header as base64-encoded JSON with `x402Version: 1`, `scheme`, `network`, and the same `payload` object, and the settlement comes back in `X-PAYMENT-RESPONSE`.

  The signing step with Blockradar is identical.
</Accordion>

### Signing rules that trip up integrations

| Rule | What happens otherwise |
| - | - |
| `domain.chainId` is a JSON number matching the wallet's chain | `400 Chain ID mismatch`. `"8453"` as a string fails too. |
| `types` holds only `TransferWithAuthorization` | Including `EIP712Domain` in `types` fails with an `ambiguous primary types or unused types` error. Blockradar builds the domain type from `domain`. |
| `message.from` is the address that signs | The facilitator rejects the payment because the signature recovers to a different address. |
| A fresh `nonce` for every payment | USDC rejects a reused nonce, so the payment cannot settle. |
| The paying address holds enough USDC | The facilitator rejects the payment with an insufficient balance reason. Gas is not needed. |

Every signature is recorded as a `SIGNED` transaction and triggers a `signed.success` [webhook](/en/essentials/signing#webhook-events), which gives you an audit trail of every payment your agent authorized. A signature is not a payment: the USDC only moves when the seller's facilitator settles it. When the seller is outside Blockradar, the settlement appears as an outgoing transfer from the agent's address. To reconcile, match `signed.success` webhooks against the on-chain transaction in the `PAYMENT-RESPONSE` header.

***

## x402 Limitations

* **EVM only.** Payments are signed with EIP-712 typed data, which Blockradar supports on EVM chains only. Solana and other non-EVM x402 networks are not supported.
* **EIP-3009 `exact` payments only.** Tokens with `transferWithAuthorization`, such as USDC, work. The `permit2` transfer method and other schemes are not covered.
* **No Circle Gateway nanopayments.** Circle's batched sub-cent payment option has not been tested with Blockradar wallets.

***

## Best Practices

* **Enforce limits in your agent.** Blockradar signs any well-formed request from a valid API key. Put per-payment and per-day limits in your agent's code, and cap total exposure with the balance of the agent's address.
* **One address per agent.** Separate budgets make it obvious which agent spent what, and let you cut one off by draining its address.
* **Check the price before signing.** Compare `amount` against the most your agent should pay for that resource, and refuse anything higher.
* **Check the recipient.** If your agent only pays known APIs, keep an allowlist of `payTo` addresses.
* **Use `metadata` and webhooks.** Tag agent addresses with `metadata`, and reconcile `signed.success` webhooks against the on-chain transaction in each `PAYMENT-RESPONSE`.
* **Lock down API access.** Accept API requests only from your own servers with [IP Whitelisting](/en/use-cases/ip-whitelist), so a leaked key cannot be used elsewhere.
* **Test on Base Sepolia first.** Use a testnet wallet, `eip155:84532`, and testnet USDC from the [faucets](/en/use-cases/supported-assets) before spending real funds.

***

## API Reference

| Endpoint | Description |
| - | - |
| [Sign Typed Data (Master Wallet)](/en/api-reference/signing/master-wallet-typed-data) | Sign a payment authorization from a master wallet |
| [Sign Typed Data (Child Address)](/en/api-reference/signing/child-address-typed-data) | Sign a payment authorization from a child address |
| [Generate Address](/en/api-reference/addresses/generate-address) | Create a dedicated address for an agent budget |
| [Update Address](/en/api-reference/addresses/update-address) | Turn auto-sweep on or off for an address |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.