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 your agent needs and how to ship the reference agent 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 remote endpoint 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.
- A few cents of USDC on Base in that project's wallet. Top up from the Pay tab.
- The URL you want to pay.
The flow
End-to-end, an SDK-based agent 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://api.example.com/search",
});
if (!paid.ok) throw new Error(`${paid.code} — ${paid.message}`);
console.log(paid.content); // what the endpoint returned
console.log(paid.txHash); // on-chain settlement proofpaid = mailgent.payments.pay(url="https://api.example.com/search")
if not paid["ok"]:
raise RuntimeError(f"{paid['code']} — {paid['message']}")
print(paid["content"]) # what the endpoint returned
print(paid["txHash"]) # on-chain settlement proofThat's the whole flow. 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://api.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 endpoint'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 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).
Note: 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 that endpoint via
_meta— no Mailgentpayments_*tool needed for that case.
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 as a bank-statement-style list, latest first. No extra scope required.
The vault is for secrets — API keys, cards, TOTP. Don't put public on-chain data in it.