protocol
selects the paywall rail: x402 (default) or mpp. Once you POST it to the
API, it shows up at a public URL anyone can pay:
protocol: "x402" (default), buyers get a 402 with x402
PAYMENT-REQUIRED. For "mpp", the unpaid response uses application/problem+json
(MPP challenge); @leashmarket/buyer-kit detects and settles either shape.
See MPP on Solana (Leash).
The link runs as a real gated HTTP resource. Buyers (or any agent driving
@leashmarket/buyer-kit) probe, pay, replay, and receive your
configured response. If metadata.upstream_url is set, the settled replay is
forwarded to that existing API and the buyer receives the live upstream response.
Funds settle directly into the owner agent’s
Asset Signer PDA (the same treasury balance you see on GET /v1/agents/{mint}/treasury/balances).
You don’t need to host anything. You don’t need to run a server. The
API is the seller for you.
When to use payment links vs the SDK
| You want… | Use this |
|---|---|
| Charge per call on a JSON endpoint without writing code | Payment links (POST /v1/payment-links) |
| Charge per call on a route in your existing Hono app | @leashmarket/seller-kit |
| Charge per call from Python / Go / Rust on your own host | Edge mode (see Monetise an API) |
| Build a one-off “Pay $X to download Y” share link | Payment links (sharable URL out of the box) |
createSeller from @leashmarket/seller-kit). The difference is purely
where the route lives — your process or ours.
Lifecycle
Every paid request also writes anearn ReceiptV1 for the owner
agent and emits receipt.published so the explorer’s per-agent
timeline lights up automatically. The chain indexer picks the
pay_to PDA up via ensureWatched the first time the link is
created, so balance changes show up too.
Create a link
Monetize an existing endpoint
To turn an API you already host into a payable endpoint, store it inmetadata.upstream_url. The response remains a fallback/template for reads and
for links without an upstream.
/x/{id} returns payment instructions. The paid
retry settles, forwards to metadata.upstream_url, and returns the upstream JSON
response to the buyer.
For POST endpoints, the buyer sends the real request body to the Leash hosted
URL. Leash settles the payment first, then forwards that same body to your
upstream URL. Creation-time metadata should only describe the expected shape.
| Field | Required | Notes |
|---|---|---|
label | Yes | Short human name. Shows up in the explorer + dashboards. |
owner_agent | Yes | Mint of the agent that receives funds. Must be on the caller-scoped network. |
method | Yes | GET / POST / PUT / PATCH / DELETE. Picks how buyers call the link. |
price | Yes | Display string. Same parser as @leashmarket/seller-kit: "$0.001", "0.5 USDT". |
currency | No | Default USDC. The primary stable used for the atomic amount. |
accepts_currencies | No | Up to 3 extra stables advertised in accepts[] (cross-stable settlement). |
response.status | No | Default 200. Status code returned on a settled call. |
response.mimeType | No | Default application/json. |
response.body | Yes | Object or string. Returned verbatim on settlement. |
webhook_url | No | Optional per-link webhook. Fires on payment_link.settled. |
wrap_receipt | No | When true, the response body wraps your template alongside the receipt. |
metadata | No | Free-form JSON returned on every read. Set upstream_url to forward paid calls and expected_request_body to describe the POST body buyers should send. |
id | No | Pick your own slug (URL-safe). When omitted, the API mints a ULID. |
parsePrice semantics as the SDK — see
Seller utilities → parse-price for
the wire-level breakdown of every accepted form.
Read a link
POST, with live counters
(call_count, settled_count, last_tx_sig, last_settled_at).
GET /v1/payment-links (no id) returns a paginated list of links
owned by the caller, newest first. Filter by ?owner_agent= and
control pagination with ?cursor=&limit=.
Update a link
410 Gone from the public paywall but stay
queryable via GET /v1/payment-links/{id} so you can re-enable them
or read past counters. Disabling emits a payment_link.updated event.
Delete a link
204 No Content. Emits a payment_link.deleted event. The
slug becomes available for reuse on the same network.
Preview a draft (no persist)
accepts[], the resolved pay_to, and the facilitator
the link would use, without writing anything to the database. UIs
use this to render a live “what buyers will see” preview while the
user is editing the form.
The public paywall: GET|POST /x/{id}
The endpoint that serves the link is anonymous on purpose — random
buyers shouldn’t need a Leash API key to pay you. It accepts the same
HTTP method you set on the link.
PAYMENT-REQUIRED (base64 → JSON) to see the offer. The
exact same shape is on accepts[] from the GET /v1/payment-links/{id}
response — UIs typically pre-render it from the API call, not from
probing the paywall.
To pay, the buyer:
- Builds an SPL
TransferCheckedfrom their treasury ATA → the link’spay_to. - Signs it.
- Replays the request with the signed transaction in the
X-PAYMENTheader.
metadata.upstream_url, the API forwards the request there and returns
the upstream response. Otherwise it returns your configured
response.body. In both cases it includes a PAYMENT-RESPONSE header
containing the tx signature.
wrong_network
hint pointing at the correct cluster. Wrong method? 405. Disabled?
410. Missing slug? 404.
The ?network= query string lets buyers force the network when
sharing devnet links. By default, the public paywall resolves to
solana-mainnet so production share URLs work without a query
string.
What lands in the explorer
Every paywall hit emits one or more events:| Event | When it fires |
|---|---|
payment_link.created | POST /v1/payment-links succeeds. |
payment_link.updated | PATCH /v1/payment-links/{id} succeeds (incl. disabled toggles). |
payment_link.deleted | DELETE /v1/payment-links/{id} succeeds. |
payment_link.served | A buyer hit the public paywall and the seller-kit returned a response. |
payment_link.settled | The buyer paid and the SPL transfer landed on chain. Carries tx_sig. |
receipt.published | The owner-agent earn receipt ingested for that settlement. |
GET /v1/events?kind=payment_link.*
and rendered on the explorer’s payment-link page at
https://explorer.leash.market/payment-links/{id} (when enabled).
Webhooks
If you want push notifications, subscribe to any of the events above viaPOST /v1/webhooks. The payment_link.settled
event in particular carries the tx_sig, the agent that received
the funds, and the payment_link_id so your billing system can
reconcile cleanly.
See also
@leashmarket/seller-kit— the same middleware, hosted in your own process.- Seller utilities —
parse-price,facilitator,networks,pay-to. Useful for validating drafts before you POST them. - Buyer endpoints — the symmetric surface for the
buyer side:
quote,payment/prepare,payment/execute. - Monetise an API — when to use payment links vs the SDK vs an edge proxy.
