Skip to content
AgentMail
AgentMail
Pay per use

x402

Pay for AgentMail per request from a crypto wallet, with no account or API key.

Your agent can pay for AgentMail per request over x402, an open protocol for HTTP payments. The wallet is the credential: a signed USDC payment authenticates each request, so the agent needs no account, no signup, and no API key.

Paid requests use the regular AgentMail API paths, starting with /v0, on dedicated hosts:

TransportHost
APIhttps://x402.api.agentmail.to
WebSocketwss://x402.ws.agentmail.to

Pick this path when your agent already holds a funded wallet. If your application has an AgentMail API key, follow the Quickstart and pay through a plan instead. If your agent’s wallet is on Tempo, pay over MPP.

0. Fund a wallet

You need Node.js 18 or later, a wallet private key, and USDC on one of the supported networks:

  • Base, Polygon, Avalanche C-Chain, or X Layer with an EVM wallet
  • Solana with a Solana wallet

This walkthrough makes one paid request: creating an inbox costs $2.00 in USDC. Every other call on this page is free.

Put the private key in an environment variable so it stays out of your code:

export EVM_PRIVATE_KEY="0x..."

1. Create a paying client

Install the AgentMail SDK and the x402 client packages for your chain:

npm install agentmail @x402/fetch @x402/evm viem

Create an x402 client, register the payment scheme for your chain, and pass it to AgentMailClient. The client selects the x402 hosts automatically. Then make a free call to check that everything works: list your inboxes.

You should get back the list of inboxes this wallet owns. On a first run it is empty.

import { privateKeyToAccount } from "viem/accounts";
import { x402Client } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { AgentMailClient } from "agentmail";

const signer = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`);

const x402 = new x402Client();
x402.register("eip155:*", new ExactEvmScheme(signer));

const client = new AgentMailClient({ x402 });

const inboxes = await client.inboxes.list();
console.log(inboxes);
Sample response
{
  "count": 0,
  "inboxes": []
}

Listing is free, but the response still proves the whole payment loop ran: the host answered with a 402 Payment Required challenge quoting a zero-amount payment, and the client signed it and retried. That signature is how AgentMail knows which wallet is calling, which is why the list contains only inboxes your wallet created.

2. Test creating an inbox with a paid request

The price is $2.00 in USDC today, and step 4 shows how to read the current price of any endpoint before you pay it.

By default the x402 client refuses to sign any payment above $1. Raise its per-payment cap to cover the price, then create the inbox. Pick your own username, or omit it and AgentMail generates one.

You should get back the new address. That means the wallet paid and the inbox exists.

x402.setSpendControls({ maxAmountPerPayment: "$2" });

const inbox = await client.inboxes.create({
  username: "wallet-agent",
});
console.log(inbox.inboxId);

This is the same loop as step 1 at a real price: the host answered the create with a 402 challenge, the client signed a $2.00 USDC payment from your wallet, and the request was retried with the payment attached. The payment is verified before AgentMail processes the request.

Sample response
{
  "organization_id": "7c9e4d2a-1f3b-4e5c-9a8d-6b7f2c1e0d4a",
  "pod_id": "7c9e4d2a-1f3b-4e5c-9a8d-6b7f2c1e0d4a",
  "inbox_id": "wallet-agent@agentmail.to",
  "email": "wallet-agent@agentmail.to",
  "created_at": "2026-08-25T10:31:47Z",
  "updated_at": "2026-08-25T10:31:47Z"
}

Save the inbox_id. You will subscribe to it in the next step. The wallet that paid for the create owns the inbox, so every later request that acts on it must be signed by the same key.

Usernames are first come, first served, and omitting username always works.

3. Test receiving events over the WebSocket

Subscribe to the inbox you created, then send it an email from your personal account.

You should see a subscribed event naming your inbox first, then a message.received event carrying the email you sent.

const socket = await client.websockets.connect();

socket.on("message", (event) => {
  if (event.type === "subscribed") {
    console.log("Subscribed to", event.inboxIds);
  }
  if (event.type === "event" && event.eventType === "message.received") {
    console.log("From:", event.message.from);
    console.log("Subject:", event.message.subject);
  }
});

socket.sendSubscribe({
  type: "subscribe",
  inboxIds: [inbox.inboxId],
});

Connecting is free. connect() fetches a zero-amount challenge from the WebSocket host, signs it with the same wallet, and attaches the credentials to the connection. Keep the process running while you wait. The email normally arrives within a couple of seconds of sending.

4. Check a price before you pay

Every endpoint quotes its own price. Send a request with no payment attached and decode the payment-required header on the 402 response, which is base64-encoded JSON:

curl -s -o /dev/null -D - -X POST https://x402.api.agentmail.to/v0/inboxes \
  | grep -i "^payment-required:" \
  | cut -d " " -f 2 \
  | tr -d "\r" \
  | base64 -d

The challenge lists one entry per supported network under accepts, each quoting the same price:

Decoded challenge (trimmed to one network)
{
  "x402Version": 2,
  "error": "Payment required",
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:8453",
      "amount": "2000000",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "payTo": "0x6e3184C204e596dED89E8A5693B602097F4Ab687",
      "maxTimeoutSeconds": 300
    }
  ]
}

amount is the price in USDC atomic units with six decimals, so 2000000 is $2.00. The challenge is the authoritative price: what it quotes at request time is what the client pays. Use it to decide the spend cap you set in step 2.

Free endpoints answer the same way with an amount of "0". Probe GET /v0/inboxes with the same pipeline to see one.

Next Steps

Was this page helpful?Suggest editsRaise issue