Starting today, an agent can call POST /v1/inboxes on Dead Simple Email with no account, no API key and no human in the loop. The API answers 402 Payment Required, the agent pays a dollar in USDC or on a Stripe card, retries the same request, and gets back an inbox. It also gets back an API key, because we think pay per request should be a door into the product, not a meter bolted onto it.
This post is the why and the how: why 402 is the authentication method that fits agents, what the headers actually look like on the wire, why the first purchase returns credentials, how the card path works for agents that hold a Stripe Link wallet, and a short comparison with AgentMail, which offers the same two protocols.
402 Is the Agent-Native Auth
Every other way of getting into an API assumes a person was there at some point. Signup forms assume a browser. API keys assume someone copied one from a dashboard. OAuth assumes a consent screen. We spent a good part of this year removing those assumptions one at a time: POST /v1/auth/agent-signup gives an agent a free trial account in one call, and Sign in with Dead Simple lets an agent log in to other apps with its inbox. But both still hand the agent a credential it has to keep.
HTTP 402 has been reserved for "payment required" since 1997 and sat unused for most of that time because there was no way to pay inline. That changed with two protocols this year. x402, from Coinbase, puts a machine-readable price list in the 402 and lets the client answer with a signed stablecoin transfer authorization; a facilitator verifies and settles it. MPP, Stripe's Machine Payments Protocol, does the same thing with a WWW-Authenticate: Payment challenge and a Shared Payment Token that represents a card or a Link wallet.
For an agent, this is the natural shape. It already has a wallet, or its operator gave it one. It does not need to know what an account is. It makes a request, reads the price, decides whether the price is worth it, pays, and gets the result. No credential to store, no key to leak, no signup to fail halfway through. An agent that has the money can use the API, full stop.
The Headers, From the Test Suite
Here is the first request, with nothing attached:
HTTP/1.1 402 Payment Required
Cache-Control: no-store
PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6MiwiZXJyb3IiOiJQYXltZW50IHJlcXVpcmVkIi...
WWW-Authenticate: Payment id="k9Qw...", realm="api.deadsimple.email", method="stripe",
intent="charge", expires="2026-09-11T14:10:00Z", request="eyJhbW91bnQiOiIxMDAi..."
{
"error": { "code": "payment_required", "price_usd": "1.00",
"message": "This call costs $1.00 USD. Pay inline with x402 ...",
"docs": "https://deadsimple.email/pay-per-request.html" },
"x402Version": 2,
"resource": { "url": "https://api.deadsimple.email/v1/inboxes",
"description": "Dead Simple Email: Create an inbox (returns an API key on first purchase)",
"mimeType": "application/json" },
"accepts": [
{ "scheme": "exact", "network": "eip155:8453", "amount": "1000000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "payTo": "0x...",
"maxTimeoutSeconds": 300, "extra": { "name": "USD Coin", "version": "2" } },
{ "...Polygon (eip155:137) and Solana mainnet entries..." }
],
"extensions": {}
}
Two protocols in one response. PAYMENT-REQUIRED is the x402 v2 object, base64 encoded; the same fields sit in the body for x402 v1 clients, which read requirements from there. WWW-Authenticate: Payment is the MPP challenge. The amount is atomic USDC, six decimals, so "1000000" is one dollar; the MPP request decodes to {"amount": "100", "currency": "usd", ...} in cents, because that is what Stripe counts in.
An x402 client picks an accepts entry, signs a transfer authorization for exactly that amount to exactly that address, and retries with the payload in PAYMENT-SIGNATURE (X-PAYMENT for v1):
POST /v1/inboxes
PAYMENT-SIGNATURE: <base64 of {"x402Version": 2, "accepted": {...the Base entry...},
"payload": {"signature": "0x...", "authorization": {"from": "0xYourWallet",
"to": "<payTo>", "value": "1000000", "validAfter": "0", "validBefore": "...", "nonce": "0x..."}}}>
{"display_name": "Paid Bot"}
HTTP/1.1 201 Created
PAYMENT-RESPONSE: <base64 of {"success": true, "transaction": "0x9f3c...", "network": "eip155:8453", "payer": "0xYourWallet"}>
{ "data": {
"inbox_id": "6f1c2d3e-8a4b-4c5d-9e6f-0a1b2c3d4e5f",
"email": "paid-bot_3e7a9c1d@box1.deadsimple.email",
"credentials": { "api_key": "dse_...", "key_id": "key_...", "account_id": "0b7d...",
"note": "Use this key (Authorization: Bearer ...) for free reads and sends within the plan; ...",
"warning": "Store this key now. It is shown once and cannot be retrieved again." },
"payment": { "transaction": "0x9f3c...", "network": "eip155:8453", "payer": "0xYourWallet" }
} }
The ordering underneath matters more than the headers. We check that the entry the client accepted matches what we offered (network, asset, recipient, amount), ask the facilitator to verify the signature and balance, and only then create the inbox. Settlement, the call that actually moves the money, happens after the inbox exists. If the facilitator says no, nothing runs. If our mail server is down and the create fails, nothing settles. The test suite asserts the exact sequence: ["verify", "settle"] on success, ["verify"] alone on failure. You cannot be charged for a request we did not complete.
Why the First Purchase Returns an API Key
A pure meter would stop there: a dollar, an inbox, and every subsequent read costs another fraction of a cent. We did price the reads ($0.005 to list or read messages, $0.01 to send), and a wallet can keep paying per call forever if it wants to. But the payer address on that first payment is a perfectly good account identifier, so we treat it as one.
The wallet keys a Pay as you go account. The first paid inbox issues that account's API key and returns it in credentials, once. Each paid inbox adds one inbox and 1,000 sends a month to the account's limits. With the key in hand, the agent reads and sends within those limits for free, exactly like any other customer; without it, the same wallet keeps paying per call and lands on the same account either way. Paid sends bypass the monthly ceiling, so a wallet is never stuck. And POST /v1/auth/claim attaches a verified email and turns the account into a regular one, custom domains and all.
That is what "a door, not a meter" means in practice. The dollar buys an inbox and an account. The per-call prices exist so that an agent never has to hold a credential if it would rather not, and so that a second inbox is one more request rather than a plan change.
What the agent does with the inbox is unchanged. It is a real address on our shared domain that receives mail, so the signup that ends in "check your email for a code" completes: the agent pays half a cent for GET /v1/inboxes/{inbox_id}/verification, or reads it for free with the key, and gets the one-time code back. Replies thread, attachments are extracted to text, and the same webhooks and streaming endpoints are available once the agent has claimed the account.
The Card Path
Not every agent has USDC. Some have a Stripe Link wallet their operator set up, or a Shared Payment Token issued by whatever platform runs them. For those, the same 402 carries an MPP challenge, and the retry is Authorization: Payment <credential>, where the credential is base64url JSON echoing the challenge plus {"spt": "spt_..."}. We charge the token with a PaymentIntent, create the inbox, and answer with a Payment-Receipt header:
POST /v1/inboxes
Authorization: Payment <base64url of {"challenge": {"id": "k9Qw...", "realm": "api.deadsimple.email",
"method": "stripe", "intent": "charge", "expires": "...", "request": "..."},
"payload": {"spt": "spt_..."}}>
HTTP/1.1 201 Created
Payment-Receipt: <base64url of {"amount": "100", "currency": "usd", "method": "stripe",
"reference": "pi_...", "status": "success", "timestamp": "2026-09-11T14:06:12Z"}>
# data.payment is {"reference": "pi_...", "method": "stripe"}; data.credentials is present as above
Cards impose two constraints we did not invent. Stripe's minimum charge is $0.50, so MPP is only offered on routes priced at or above that, which today means inbox creation at $1.00. And a card has no stable payer identity the way a wallet address does, so a card purchase creates its own account and returns a key rather than trying to recognise you next time. Cards also have to be charged before the work, not after. So if inbox creation fails after the charge, the PaymentIntent is refunded automatically; the tests cover that path too, asserting the refund call lands right after the failed create.
The challenge itself is an HMAC over its own fields, expires in five minutes, and is accepted exactly once. A tampered amount, an expired challenge or a replayed one is refused before anything reaches Stripe. If you want to try it from a terminal, npx @stripe/link-cli mpp pay https://api.deadsimple.email/v1/inboxes does the whole exchange with a Link wallet.
Using It From Code
The official x402 clients turn the loop into one line: @x402/fetch wraps fetch with a viem wallet client on Base, and the Python x402 package (pip install "x402[evm,httpx]") ships httpx hooks that take an eth_account signer. mppx on npm and pympp on PyPI do the same for MPP. Snippets for all of them are on the product page, along with the raw curl loop for anyone who would rather not add a dependency. And if an agent wants a key without paying anything, POST /v1/auth/agent-signup is still there: a free trial account, key and live inbox in one unauthenticated call.
How This Compares to AgentMail
AgentMail supports both x402 and MPP for inbox creation as well, and we think that is the right call; two providers speaking the same two protocols is better for agents than one. The visible differences are price and what comes back. AgentMail charges $2.00 per inbox over x402. We charge $1.00, and the first paid inbox also returns an API key for a wallet-keyed account with per-inbox capacity, so an agent can decide after the fact whether it wants to keep paying per call or switch to the key. We also price the reads and sends individually, so an agent that only ever wants to pay per request can. Beyond that the protocols are the protocols: same headers, same facilitator model, same Stripe challenge, and any client that works against one of us should work against the other.
Where to Start
Point a wallet at POST /v1/inboxes. If you are writing the client yourself, the product page has the full 402 body, the networks and USDC contract addresses, the retry headers and the receipts. If you would rather not think about payments at all, sign up: five inboxes are free, no card.