Pay for tools
Your agent pays for paid HTTP endpoints and MCP tools in USDC on Base
You're building an agent that calls paid endpoints — paid search APIs, paid MCP tools, anything that responds with HTTP 402. This page covers what the buyer side looks like, what your agent needs, and how to ship the reference buyer in five minutes.
How it works
Hand Mailgent an x402 URL. Mailgent runs the handshake on your project's Kernel smart account on Base — checks your mandate caps, signs the EIP-3009 USDC transfer, retries with X-Payment, returns { ok, content, txHash }.
Your agent never holds a signing key; the seller never sees your Mailgent API key. The mandate is the spend policy, the chain is the source of truth.
Before you start
- A Mailgent project with Pay → Spend turned on. This is typically a separate project from any seller projects you operate.
- A few cents of USDC on Base in that project's wallet. Top up from the Pay tab.
- The seller's URL — must exactly match what they registered with Mailgent.
The flow
End-to-end, an SDK-based buyer is three steps: install, set a mandate once, then pay. The SDK does the rest.
1. Install
npm install @mailgent/sdkpip install mailgent-sdk2. Set a mandate (one-time)
A mandate is the wallet's spend policy — maxPerCallUsdc and dailyCapUsdc. Mailgent enforces it server-side on every pay() call, so a runaway loop can't drain the wallet. Create one once (installing the on-chain session key takes 10–30s), then reuse it forever. Skip this step if you already set a mandate in the Pay tab.
import { Mailgent } from "@mailgent/sdk";
const mailgent = new Mailgent({ apiKey: process.env.MAILGENT_API_KEY! });
const { mandates } = await mailgent.payments.mandates.list();
let mandate = mandates.find((m) => !m.revokedAt && !m.installError);
if (!mandate) {
mandate = await mailgent.payments.mandates.create({
maxPerCallUsdc: "0.10",
dailyCapUsdc: "1.00",
});
if (mandate.installError) throw new Error(mandate.installError);
}import os
from mailgent import Mailgent
mailgent = Mailgent(api_key=os.environ["MAILGENT_API_KEY"])
mandates = mailgent.payments.mandates.list().get("mandates", [])
mandate = next(
(m for m in mandates if not m.get("revokedAt") and not m.get("installError")),
None,
)
if mandate is None:
mandate = mailgent.payments.mandates.create(
max_per_call_usdc="0.10",
daily_cap_usdc="1.00",
)
if mandate.get("installError"):
raise RuntimeError(mandate["installError"])Full mandate reference: payments.mandates.
3. Pay
const paid = await mailgent.payments.pay({
url: "https://seller.example.com/search",
});
if (!paid.ok) throw new Error(`${paid.code} — ${paid.message}`);
console.log(paid.content); // what the seller returned
console.log(paid.txHash); // on-chain settlement proofpaid = mailgent.payments.pay(url="https://seller.example.com/search")
if not paid["ok"]:
raise RuntimeError(f"{paid['code']} — {paid['message']}")
print(paid["content"]) # what the seller returned
print(paid["txHash"]) # on-chain settlement proofThat's the whole buyer. payments.pay() returns a typed discriminated union — branch on ok. The 17 possible code values for failures are listed in the payments.pay reference.
Or: clone the reference agent
If you'd rather start from a working repo: mailgent-dev/mailgent-pay-examples has the Node (payment-agent) and Python (payment-agent-python) versions ready to run with .env + npm start / python main.py.
From the CLI
For shell scripts, CI jobs, or one-off testing, the @mailgent/cli package exposes the same three steps as commands. Install once with npm install -g @mailgent/cli, then:
export MAILGENT_API_KEY=loid-...
# 1. Set a mandate (one-time, ~10–30s while the session key lands on Base).
mailgent mandate create --max-per-call 0.10 --daily-cap 1.00
# 2. Pay an x402 URL.
mailgent pay https://seller.example.com/search
# 3. Bank-statement-style spend/income feed.
mailgent activity --limit 10Output is human-readable tables; add --json to any command for machine-parseable JSON. Mandate management is a full CRUD: mailgent mandate list, mailgent mandate get <id>, mailgent mandate revoke <id>.
mailgent pay --dry-run <url> validates your mandate + balance against the seller's price without spending — useful for checking caps before automating.
From MCP (Claude Desktop, Cursor, ChatGPT)
If your agent talks to Mailgent via MCP, the same operations are available as tools — no SDK install needed. Wire up Mailgent as an MCP server (see MCP setup) and these become callable:
| Tool | What it does | Scope |
|---|---|---|
payments_pay | Pay an x402 URL within the active mandate | payments:spend |
payments_mandates_create | Create a spend mandate | payments:spend |
payments_mandates_list | List mandates for this identity | payments:spend |
payments_mandates_get | Fetch one mandate with live counters | payments:spend |
payments_mandates_revoke | Revoke a mandate | payments:spend |
payments_activity | Bank-statement-style spend/income feed | (none) |
Like the SDK, payments_pay returns a discriminated { ok, ... } body — agents should branch on ok and read code on failure rather than treating non-success as an exception.
Self-hosting MCP? The same tool surface ships in @mailgent/mcp, a stdio MCP server you can run locally (npx -y @mailgent/mcp).
Tangent — paid MCP tools as a seller: If your agent is consuming an MCP tool that itself charges per call (a paid search MCP, etc.), the MCP client speaks x402 directly with the seller via _meta — no Mailgent payments_* tool needed for that case. Mailgent sits on the seller side there, via @mailgent/sdk/paywall/mcp.
Where the proof lives
You don't need to store anything yourself. Every successful payments.pay() leaves three independent records:
- In the response —
paid.txHash,paid.cost,paid.recipient,paid.resource, pluspaid.mandate.remainingTodayUsdcRawso you can see what's left in the daily cap. - On Base —
txHashis the on-chain USDC transfer. Anyone with the hash can verify amount, payer, and recipient on a block explorer or via RPC. The chain is the canonical record. - In Mailgent —
mailgent.payments.activity()(orGET /v0/payments/activity) returns your project's full spend history (and income, if you're also a seller) as a bank-statement-style list, latest first. No extra scope required.
The seller's Ed25519-signed receipt is a seller-side artifact (retrievable via GET /v0/payments/:id on the seller's project) — useful when you are the seller reconciling income, not when you're the buyer reconciling spend.
The vault is for secrets — API keys, cards, TOTP. Don't put public on-chain data in it.