A payment link is a small JSON document describing one paid HTTP endpoint: a method, a path, a price, an accepted stablecoin set, and either a response template or a stored upstream URL to forward to after payment. Optional 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:
https://api.leash.market/x/{id}
For 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.
You want…Use this
Charge per call on a JSON endpoint without writing codePayment 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 hostEdge mode (see Monetise an API)
Build a one-off “Pay $X to download Y” share linkPayment links (sharable URL out of the box)
Payment links and the SDK use the same underlying middleware (createSeller from @leashmarket/seller-kit). The difference is purely where the route lives — your process or ours.

Lifecycle

Every paid request also writes an earn 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.
POST /v1/payment-links
Authorization: Bearer lsh_test_...
Content-Type: application/json

{
  "label": "Tag a payload",
  "owner_agent": "BcN4ToBs8jE3dbYNhYqDJqGnKPjH3zRX8gsDUDH72JQp",
  "method": "POST",
  "price": "$0.001",
  "currency": "USDC",
  "accepts_currencies": ["USDG"],
  "protocol": "x402",
  "response": {
    "status": 200,
    "mimeType": "application/json",
    "body": { "ok": true, "tagged": true }
  }
}
Response:
{
  "id": "01HVTQX4GZ",
  "network": "solana-devnet",
  "label": "Tag a payload",
  "owner_agent": "BcN4…",
  "pay_to": "9pK9…",
  "method": "POST",
  "path": "/x/01HVTQX4GZ",
  "price": "$0.001",
  "currency": "USDC",
  "accepts_currencies": ["USDG"],
  "facilitator": "https://facilitator-devnet.leash.market",
  "share_url": "https://api.leash.market/x/01HVTQX4GZ",
  "accepts": [
    {
      "scheme": "exact",
      "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc",
      "pay_to": "9pK9…",
      "asset": "4zMM…",
      "amount": "1000",
      "currency": "USDC"
    },
    {
      "scheme": "exact",
      "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc",
      "pay_to": "9pK9…",
      "asset": "USDG…",
      "amount": "1000",
      "currency": "USDG"
    }
  ],
  "counters": {
    "call_count": 0,
    "settled_count": 0,
    "last_called_at": null,
    "last_settled_at": null,
    "last_tx_sig": null
  },
  "created_at": "2026-04-25T01:00:00.000Z"
}

Monetize an existing endpoint

To turn an API you already host into a payable endpoint, store it in metadata.upstream_url. The response remains a fallback/template for reads and for links without an upstream.
POST /v1/payment-links
Authorization: Bearer lsh_test_...
Content-Type: application/json

{
  "label": "JSONPlaceholder posts",
  "owner_agent": "BcN4ToBs8jE3dbYNhYqDJqGnKPjH3zRX8gsDUDH72JQp",
  "method": "GET",
  "price": "$0.001",
  "currency": "USDC",
  "protocol": "x402",
  "response": {
    "status": 200,
    "mimeType": "application/json",
    "body": {
      "ok": true,
      "message": "Payment accepted. Call the protected endpoint to receive live data."
    }
  },
  "metadata": {
    "upstream_url": "https://jsonplaceholder.typicode.com/posts",
    "provider_url": "https://jsonplaceholder.typicode.com",
    "pricing_type": "fixed"
  }
}
At runtime the first call to /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.
{
  "method": "POST",
  "metadata": {
    "upstream_url": "https://api.example.com/design",
    "expected_request_body": {
      "prompt": "string",
      "style": "string",
      "format": "string"
    }
  }
}
The actual paid request is sent later by the buyer:
POST /x/design-agent?network=solana-devnet
Content-Type: application/json
X-PAYMENT: <payment-proof>

{
  "prompt": "Design a landing page for a Solana app",
  "style": "premium dark mode",
  "format": "html"
}
The fields you set:
FieldRequiredNotes
labelYesShort human name. Shows up in the explorer + dashboards.
owner_agentYesMint of the agent that receives funds. Must be on the caller-scoped network.
methodYesGET / POST / PUT / PATCH / DELETE. Picks how buyers call the link.
priceYesDisplay string. Same parser as @leashmarket/seller-kit: "$0.001", "0.5 USDT".
currencyNoDefault USDC. The primary stable used for the atomic amount.
accepts_currenciesNoUp to 3 extra stables advertised in accepts[] (cross-stable settlement).
response.statusNoDefault 200. Status code returned on a settled call.
response.mimeTypeNoDefault application/json.
response.bodyYesObject or string. Returned verbatim on settlement.
webhook_urlNoOptional per-link webhook. Fires on payment_link.settled.
wrap_receiptNoWhen true, the response body wraps your template alongside the receipt.
metadataNoFree-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.
idNoPick your own slug (URL-safe). When omitted, the API mints a ULID.
Pricing uses the same parsePrice semantics as the SDK — see Seller utilities → parse-price for the wire-level breakdown of every accepted form.
GET /v1/payment-links/{id}
Authorization: Bearer lsh_test_...
The response is the same shape as 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=.
PATCH /v1/payment-links/{id}
Authorization: Bearer lsh_test_...
Content-Type: application/json

{
  "label": "Tag v2",
  "price": "$0.002",
  "response": { "status": 200, "mimeType": "application/json", "body": { "ok": true, "v": 2 } }
}
Any subset of the create-time fields can be patched. To soft-disable a link without losing analytics:
{ "disabled": true }
Disabled links return 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 /v1/payment-links/{id}
Authorization: Bearer lsh_test_...
Returns 204 No Content. Emits a payment_link.deleted event. The slug becomes available for reuse on the same network.

Preview a draft (no persist)

POST /v1/payment-links/preview
Authorization: Bearer lsh_test_...
Content-Type: application/json

{
  "owner_agent": "BcN4…",
  "method": "POST",
  "price": "$0.001",
  "currency": "USDC",
  "accepts_currencies": ["USDG"]
}
Returns the 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.
# First request — no payment header.
curl -i https://api.leash.market/x/01HVTQX4GZ
# HTTP/1.1 402 Payment Required
# PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6Mi…
Decode 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:
  1. Builds an SPL TransferChecked from their treasury ATA → the link’s pay_to.
  2. Signs it.
  3. Replays the request with the signed transaction in the X-PAYMENT header.
The API hands the signed tx to the configured facilitator and the facilitator settles the SPL transfer on chain. If the link has 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.
# After settlement.
curl -i -H "X-PAYMENT: <signed-base64>" https://api.leash.market/x/01HVTQX4GZ
# HTTP/1.1 200 OK
# PAYMENT-RESPONSE: eyJ0cmFuc2FjdGlvbiI6IjV4WTcuLi4iLC…
# Content-Type: application/json
#
# {"ok":true,"tagged":true}
Wrong network in the URL? The API returns a 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:
EventWhen it fires
payment_link.createdPOST /v1/payment-links succeeds.
payment_link.updatedPATCH /v1/payment-links/{id} succeeds (incl. disabled toggles).
payment_link.deletedDELETE /v1/payment-links/{id} succeeds.
payment_link.servedA buyer hit the public paywall and the seller-kit returned a response.
payment_link.settledThe buyer paid and the SPL transfer landed on chain. Carries tx_sig.
receipt.publishedThe owner-agent earn receipt ingested for that settlement.
All of them are queryable via 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 via POST /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 utilitiesparse-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.