Seller hello world
A numbered tour: copy the sidecar, bind it to the gateway, ingest the origin in the console, then Publish. Sui Agent Payments places the priced 402 and settlement boundary at the gateway. Your raw upstream is never called until a verified delivery proof arrives.
There is no seller SDK to install. The template is the integration.
1. Copy the template
pnpm -C apps/seller-template dev
# or
docker build -f apps/seller-template/Dockerfile -t suipay-seller .
docker run --rm -p 8089:8089 -e SUIPAY_BIND_HOST=0.0.0.0 suipay-sellerDo not copy apps/seller-openrouter, apps/seller-gcp, apps/seller-aws,
or apps/seller-walrus. Those are first-party Railway products (gateway-seeded).
2. Bind the gateway with env
Set SUIPAY_GATEWAY_ISSUER and SUIPAY_GATEWAY_JWKS_URL to the exact
issuer origin (the URL the gateway puts on delivery-proof JWTs).
localhost and 127.0.0.1 are different. The seller binds JWKS from those
values. GET /config and GET /config.json are a JSON snapshot, not a form.
SUIPAY_GATEWAY_ISSUER=https://gateway.example
SUIPAY_GATEWAY_JWKS_URL=https://gateway.example/.well-known/jwks.jsonLoopback http://localhost:4340 is allowed. Production JWKS must be HTTPS.
Paid routes answer 503 gateway_not_configured until this succeeds.
3. Paste the seller origin in the console
The console server-fetches GET /.well-known/suipay-seller (alias
/setup.json) and fills the draft (path, price, rail, mpp / x402).
Fetching a URL does not register or list the service. First-party catalog
ids are refused — they are already on Directory.
POST /v1/probe is the other surface. This managed origin does not mint a 402,
so probe against it fails.
4. Register, then Publish
The gateway's canonical registration endpoint is POST /v1/register. The
console may expose POST /gateway/api/register as its seller-facing proxy.
Register with the same id the template guards.
Registration creates a draft (unlisted, untrusted, unsubmitted).
Activate it through the authenticated owner surface:
POST /gateway/api/services/:id/status { "status": "live" } — the console
Publish button. A live service appears in GET /v1/services filtered
to live, and on /directory and /services/[slug]. Draft, paused, and revoked
services cannot mint new challenges.
A registration identifies at least:
| Field | Notes |
|---|---|
| Service identity | id (the slug you guarded), name, description |
| Public method and path | What buyers call on the gateway |
| Upstream origin | Stored origin only; never taken from the request at delivery |
| Price | Atomic integer string (no floats) |
| Asset, decimals, network | Fully qualified coin type; e.g. sui:testnet |
Recipient (payTo) | Must match policy targets buyers will be allowed to pay |
| Dialects | e.g. ["mpp"] |
| Settlement rail | direct, or spend account (delegate on the wire) |
| MIME type | Response type buyers expect |
Listing does not append the origin to SUIPAY_UPSTREAM_ALLOW. Publish
does not allowlist. The operator adds origins by hand. A listed service
that is not on the allowlist still settles; the gateway returns synthesized
JSON, not Echo. The 503 upstream_not_ready readiness gate does not apply
to a listed service (managed: false) unless the operator names its id in
SUIPAY_READINESS_SERVICE_IDS.
Pricing tips
- Quote prices only in atomic units as decimal strings (
"250000"not0.25). - Keep
asset+decimalsfor display; authorization always uses the integer. - Changing price or binding fields creates a new service revision; offers embed the revision the buyer paid for.
Rail choice
| Rail | When |
|---|---|
Spend account (delegate on the wire) | Default for agents (MPP pay-then-prove) |
direct | Payer spends their own coin (no owner spend account) |
recurring | Recurring mandate (intent: subscription) |
allowance | Do not use (retired) |
5. Return one 402
Point buyers at the gateway URL (resourceUrl from discovery). An unpaid
request receives one 402. The template's discovery document suggests
rail: "delegate" with dialects: ["mpp", "x402"]. The gateway refuses x402 on
a delegate registration (INVALID_DIALECT) unless SUIPAY_SPEND_ACCOUNT_X402
is on; without it, register dialects: ["mpp"]. With it on, a 402 carries x402
only when the gateway can lease sponsor gas and pin it in that offer; otherwise
the 402 is MPP only, as below. Sui Agent Payments binds method, path, and on body-bearing
requests the body hash and normalized content type into the signed offer. The
seller template does not mint a second 402.
HTTP/1.1 402 Payment Required
WWW-Authenticate: Payment method="sui.charge", challenge="...", amount="...", ...
Content-Type: application/jsonSee HTTP 402 reference. After a buyer pays, the gateway settles and calls your upstream with the delivery proof the template verifies.
6. Reconcile receipts
Query GET /v1/settlements (with supported filters) and retain the immutable
receipt: challenge ID, transaction digest, network, payer, atomic amount,
asset, and request digest when present. A successful payment and successful
upstream delivery are separate outcomes; keep the receipt when delivery fails
and do not ask the buyer to pay again without reconciliation.
Verify digests on Suiscan. See Receipts.
Full contract: Seller template.