Skip to content
LogoLogo

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-seller

Do 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.json

Loopback 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:

FieldNotes
Service identityid (the slug you guarded), name, description
Public method and pathWhat buyers call on the gateway
Upstream originStored origin only; never taken from the request at delivery
PriceAtomic integer string (no floats)
Asset, decimals, networkFully qualified coin type; e.g. sui:testnet
Recipient (payTo)Must match policy targets buyers will be allowed to pay
Dialectse.g. ["mpp"]
Settlement raildirect, or spend account (delegate on the wire)
MIME typeResponse 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" not 0.25).
  • Keep asset + decimals for 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

RailWhen
Spend account (delegate on the wire)Default for agents (MPP pay-then-prove)
directPayer spends their own coin (no owner spend account)
recurringRecurring mandate (intent: subscription)
allowanceDo 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/json

See 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.