Sell an endpoint to an agent
Gate your own URL behind x402 so an autonomous payer is charged per request.
An autonomous agent cannot open an account, hold a card, or be asked to understand gas. x402 gives it one thing to sign: an authorization for exactly this request, at exactly this price.
Register the endpoint in the dashboard under Agent endpoints first. That prices it and records where you are paid — it does not change your server, which still answers every request for free. This page is the part that closes the gate.
What your server does
Three steps, in this order:
- A request with no payment: fetch the price from Mayarin and return
402carrying it. - A request carrying
PAYMENT-SIGNATURE: settle it. Verification is part of settling — Mayarin checks the signature against the chain as it broadcasts, so a separateverifyround-trip is only for a payer’s “would this go through?” question. - Only then serve the content.
The order is not stylistic. A response cannot be un-served, so a handler that ran first would have to be undone if the settlement failed — and there is no undo for bytes already on the wire.
The SDK gate
In TypeScript the whole thing is two imports and one middleware:
import { createX402Gate } from "@mayarin/sdk";
import { x402Connect } from "@mayarin/sdk/x402/connect";
const gate = createX402Gate({
baseUrl: "https://api.mayarin.xyz",
resourceId: "your-resource-id",
});
app.get("/premium", x402Connect(gate), paidHandler);Unpaid is answered with the price in the standard PAYMENT-REQUIRED header; a settled payment runs the handler once with PAYMENT-RESPONSE on the answer; a replayed signature is re-served without a second charge or a second handler run; and if Mayarin cannot be reached the answer is 502 and the handler never runs. The SDK page covers registration, the decision table, and the delivery policy. A runnable server lives in the repo as apps/x402-merchant.
The HTTP contract, for stacks without a gate
Written against fetch and the two header names from the specification, so it ports to any language or framework by changing how a request and a response are spelled.
const MAYARIN = "https://api.mayarin.xyz";
const RESOURCE = "your-resource-id";
export async function handle(request) {
const signature = request.headers.get("PAYMENT-SIGNATURE");
// 1. Nothing paid yet: answer 402 with the price Mayarin quotes.
if (!signature) {
const required = await fetch(
`${MAYARIN}/x402/resources/${RESOURCE}/payment-required`,
).then((r) => r.json());
return new Response(null, {
status: 402,
headers: { "PAYMENT-REQUIRED": btoa(JSON.stringify(required)) },
});
}
// 2. Settle. Verification is part of settling; never serve first.
const paymentPayload = JSON.parse(atob(signature));
const settled = await fetch(`${MAYARIN}/x402/resources/${RESOURCE}/settle`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
x402Version: 2,
paymentPayload,
paymentRequirements: paymentPayload.accepted,
}),
}).then((r) => r.json());
if (!settled.success) return new Response(null, { status: 402 });
// 3. Paid. Serve it, and say which transaction paid for it.
return new Response(yourContent, {
headers: { "PAYMENT-RESPONSE": btoa(JSON.stringify(settled)) },
});
}What Mayarin does, so your server does not
No key and no chain code run on your side. Mayarin rebuilds the payment requirements from the registered resource rather than trusting the copy the payer sent, checks the signature against the chain, broadcasts the transfer, and posts the double-entry records that credit you.
That last point is worth stating plainly: the facilitator verifies against its own requirements. A facilitator that verified a payment against the requirements handed to it would be verifying a payment against its own claims.
Nothing secret rides on a payment either. The request-time surface is public and keyless — your secret key registers the resource and never appears in a gate. A payer who has captured every byte of a request has captured nothing that spends.
Delivery and replay policy
One authorization buys one execution. A payer whose HTTP response was lost on the way back retries with the identical signature, and the gate re-serves the recorded response — no second charge, no second handler run. Replays are keyed by the payment’s nonce and expire at the authorization’s validBefore, the whole window in which a retry is legal.
The SDK’s store is in memory, per process, bounded by live payments. After a restart it is empty: a replayed signature reaches Mayarin, which refuses the spent nonce fail-closed — still no second charge, but no re-serve either. Your handler’s own side effects are your idempotency to own: the gate guarantees one execution per authorization, not one execution per retry.
The recorded response is re-served in full, so gate content that streams or answers range requests.
Payables: what an agent can find, and pay
An endpoint is not the only thing an agent can buy. An invoice or a payment link you already sell — the same ones a person pays through the hosted checkout — can be paid by an agent over x402, at the URL of the obligation itself:
GET /x402/payables/invoice/invc_123 → 402 quoting the outstanding balance
GET /x402/payables/link/plnk_abc → 402 quoting the link's totalThe 402 always quotes the full outstanding amount. An agent cannot decide to pay part of an invoice: an authorization for less than what is owed is refused, and the remedy is a new 402 for the whole balance.
Discovery is the other half. GET /x402/payables lists every payable across every merchant that opted in — newest first, cursor-paginated — so an agent that has not met anyone yet can find what to pay without being told a merchant id. An entry carries what an agent needs to decide: merchant, title, the price a 402 would quote, the payment URL, and the due date when there is one.
Nothing is listed by default. Opt in per obligation:
| Endpoint | Effect |
|---|---|
POST /v1/invoices/:id/list | The invoice appears in the payable index |
POST /v1/payment-links/:id/list | The link appears in the payable index |
POST /v1/x402/resources/:id/list | The endpoint appears in the resource index |
Each has an /unlist twin. Listing is only about being found: an unlisted invoice is still payable by anyone holding its id, because the unguessable id is the access control — the same rule a payment link already follows. Draft, void and paid-in-full invoices, disabled and expired links, and open-amount links never appear and never quote, listed or not.
The quote lock
A payable’s price is held for a short window (default sixty seconds) in a quote row, together with the exact rails the 402 offered. Two consequences an agent will meet:
A quote expires. Signing later than the window gets a 410 with the remedy in it: request a new 402 at the same URL.
One authorization at a time. Two agents quoting the same invoice and both signing is a race the lock decides: the first signature to settle claims it, the second is refused with a 409. A signature for a different amount than was quoted — the balance changed in between — is also a 409, because the payer signed a number nobody honoured any more.
Buying over MCP, not just over HTTP
POST /x402/mcp is an MCP server on the same rail — the shape an agent’s tooling already speaks. It connects, reads what the tools do, calls one, receives a 402, signs one authorization, and gets its answer.
| Method | Costs |
|---|---|
initialize, tools/list | Free |
tools/call | One authorization |
Discovery is free and answers are paid. An agent cannot decide a price is worth paying for a tool it has not been allowed to read the description of; a 402 on the catalogue is a shop with the lights off.
The server sells rail intelligence — how each payment rail has actually been settling, measured rather than asserted. rail_stats reports samples, median headroom, and the worst and best observed per rail; choose_rail ranks them and returns the one to pay on with the reason in a line. Headroom is the seconds an order had left before its deadline when it landed, so the minimum is the number that matters — a median on its own calls a rail comfortable when its worst settlement landed with seconds to spare.
Three refusals never charge, and they are worth knowing if you build against it:
- Arguments the tool will not accept — a chain this deployment has never heard of — are refused before the gate.
exactgives a payer one signature and no way to get it back, and a resource server cannot un-serve a response. - No settlements observed at all is refused rather than sold. Charging for “no rail has been observed” is charging for an outage.
- A tool that does not exist is a tool error, not a JSON-RPC error, so a model can tell “the server said no” from “the call never arrived” and decide whether retrying is worth anything.
Trying the free half needs no wallet:
curl -X POST https://api.mayarin.xyz/x402/mcp -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'When you pay, send the same body in both the request that receives the 402 and the retry carrying the signature. A different one would be a different purchase settled against the first one’s authorization.
Endpoints
Both are public and unauthenticated. An agent that has never met Mayarin is the entire point, and a price is not a secret.
| Endpoint | What it answers |
|---|---|
GET /x402/payables | Every listed payable across every merchant, newest first |
GET /x402/payables/:kind/:id | The 402 for one invoice or link — or, with a signature, settlement |
GET /x402/resources | Every listed resource across every merchant, newest first |
GET /x402/resources/:id/payment-required | The price and rails, without making the call you are deciding about |
GET /x402/resources/:id/rail | Which rail was listed first, and the measurements behind that ordering |
POST /x402/resources/:id/verify | Would this authorization go through? |
POST /x402/resources/:id/settle | Broadcast it, and credit the merchant |
GET /x402/resources?merchant=<id> | What one merchant sells, for an agent shopping |
POST /x402/mcp | The MCP server — free discovery, paid tools/call |
Things that will cost you an afternoon
The registered URL must be exactly the one an agent calls. A resource is looked up by URL — scheme, host and path all count.
A 402 is a price, not a promise. It is honoured for maxTimeoutSeconds, and the quote lock can be shorter. An agent that signs a stale authorization gets it refused.
Cross-asset rails pay the operator, not you. transferWithAuthorization names one recipient, chosen before the payer signs, so a payer holding an asset you do not settle in sends it somewhere Mayarin can swap from. You are still paid your own asset, in full, and the difference is booked as the payer’s surplus.
A contract account is a valid payer. An agent wallet from a provider like Circle is a smart account: its signature validates through EIP-1271 and recovers to its signer rather than to itself. Mayarin asks the chain rather than recovering locally, so these payments verify — but a gate of your own that tried to check the signature would reject every one of them as forged. Do not check it; asking the chain is what settling is for.