# Deploy by Suize — agent-native site hosting > POST a built static site, get a live hash-verified URL on decentralized > storage in seconds. Built for agents: one HTTP flow, no browser, no gas. > Deploy is a merchant on the Suize rail — every charge is paid in USDC over > x402, gaslessly; the agent signs the payment itself with its own Sui key. > This file is your contract. What you get: - Your site live at `https://.suize.site` (`id` derives from the site's on-chain record). - Content integrity: every served byte is verified against the on-chain record at serve time — a mismatch is never served. - Each deploy mints a NEW immutable site (new id → new URL). No overwrites, no silent edits. ## How you pay — one contract, every step You speak the x402 contract directly against `https://api.suize.io` and sign the payment **yourself** with your own Sui key — **your address is the account**: no SDK, no Suize account, no API key, no human, no pay-link. Bring USDC; you never need a gas token. Every paid step is the same three moves: 1. Make the request with no payment → you get **HTTP 402** carrying a vanilla x402 V2 `PaymentRequired` challenge. Read the `exact` requirement on the Sui network: `payTo`, `asset` (USDC), `amount` (in atomic units, 6 dp), and `extra.outputs` — the exact balance-change set you must pay. 2. Settle it **gaslessly**. Let Suize build the transfer for you — `POST /build { sender, outputs }` (where `outputs` is the challenge's `extra.outputs`, **verbatim**) → `{ bytes }` — then **sign `bytes` OPAQUELY** with your own key (decode the base64 to raw bytes, sign those, re-encode the signature; do **NOT** BCS-decode or re-serialize the bytes). Or, if you're Sui-native, build + sign the same gasless `send_funds` transfer yourself. 3. Retry the original request carrying `X-PAYMENT: `. The payment carries no gas token, ever. The settlement's on-chain balance changes are your receipt — read them yourself; the rail fee is one of the declared outputs and visible there. Audit any digest via `GET https://api.suize.io/tx?digest=`. ## The `X-PAYMENT` wire — exact shape (no source-reading required) `X-PAYMENT` is `base64( JSON.stringify(payload) )` — **standard base64, WITH padding** (not base64url, not padding-stripped). `payload` is the x402 V2 `PaymentPayload`: ```json { "x402Version": 2, "accepted": , "payload": { "transaction": "", "signature": "" }, "extensions": } ``` Three rules that save a dozen probe round-trips: - **`transaction` is the `/build` bytes byte-for-byte.** Decode them to raw bytes ONLY to sign; never BCS-decode + re-serialize (a re-serialize with a skewed SDK is exactly what makes `/verify` answer `Unknown TypeTag …`). Sign, don't re-pack. - **`signature`** = `base64( yourKeypair.signTransaction(rawBytes).signature )` — the Sui SDK's `signTransaction` already returns the base64 signature; just embed it. - **`accepted` and `extensions`** are echoed from the 402 challenge unchanged — the facilitator deep-equals `accepted` against the terms it issued and matches the `payment-identifier` in `extensions`. ### A complete worked deploy (every step) ```bash # 1 · discover the price (bare POST → 402) curl -s -X POST https://api.suize.io/deploy # → {"x402Version":2,"accepts":[{ … ,"payTo":"0x…","amount":"500000", # "extra":{"outputs":[{"to":"0x…","amount":"500000"}],"buildUrl":"https://api.suize.io/build"}}], # "extensions":{"payment-identifier":{"info":{"id":"…"}}}} # 2 · build the gasless bytes — outputs = the challenge's extra.outputs, verbatim curl -s -X POST https://api.suize.io/build \ -H 'content-type: application/json' \ -d '{"sender":"0x","outputs":[{"to":"0x…","amount":"500000"}]}' # → {"bytes":""} # 3 · SIGN opaquely (the one non-curl step, any Sui keypair / zkLogin session): # sig = base64( keypair.signTransaction( fromBase64(bytes) ).signature ) # 4 · X-PAYMENT = base64( JSON.stringify({ # x402Version: 2, accepted, payload: { transaction: bytes, signature: sig }, extensions # }) ) // standard base64, with padding # 5 · upload the tar carrying X-PAYMENT (the SAME request as the 402; tar = ustar of your build dir) curl -s -X POST https://api.suize.io/deploy \ -H "X-PAYMENT: " \ -F name=my-site \ -F site.tar=@site.tar # → {"siteId":"0x…","subdomain":"…","url":"https://….suize.site","version":1,"digest":"…"} ``` That's the entire contract — five steps, four of them plain `curl`, one local signature. No SDK is required beyond whatever signs a Sui transaction for you. ## 1. Deploy a site — $0.50 ($0.10 for subscribers) `$0.50` (USDC) is the standard deploy amount. If the **paying account holds an active Deploy subscription** (stage 3), each deploy is **$0.10** instead. Two ways to use it: add `?sender=` to the `POST /deploy` discovery shot and the 402 quotes your rate automatically; or just build a single `$0.10` (`100000`) output to the treasury. Either way the facilitator **re-checks your subscription against the chain at verify** — a non-subscriber presenting `$0.10` is rejected, and a subscriber may always pay the full `$0.50` too. The deploy is **two requests**: an unpaid one to get the 402, then the paid multipart upload. 1. `POST /deploy` with no payment → **402**. Build + sign the payment with the moves above, but DO NOT settle it yourself — present the signed `PaymentPayload` on the upload (the backend settles it during the deploy). 2. `POST /deploy` again as `multipart/form-data`, carrying the `X-PAYMENT` header (your base64 signed `PaymentPayload`) plus: - `site.tar` — a tar (ustar) of your BUILT static-site directory (`index.html` at the root; caps: 100 MiB, 2000 files). - `name` — your site label. The payment IS the authorization — there is no separate signature. **Whoever pays owns the site**: the recovered payer becomes the on-chain `owner`. (If you are Sui-native you may pre-settle a normal payment and present that `X-PAYMENT`; the recommended path is to present the SIGNED-UNSETTLED payload and let the deploy settle it, so nothing is public before the deploy — there is nothing to replay.) → `{ siteId, subdomain, url, version, digest }`. Live at `url` immediately. **One payment mints one site, enforced on-chain.** A retry after a transient failure re-presents the same payment and re-mints (the site only consumes the payment when it actually mints); a second, distinct site needs a second payment. A double-submit of a payment that already minted a site is rejected (`409`). Read back: `GET /sites?owner=
` lists your sites; `GET /sites/` returns one, including `expiresAtMs` + `storageEndEpoch` (when its storage window lapses, as both wall-clock ms and the Walrus end epoch). ## Storage lifetime — a deploy lasts ~30 Walrus epochs A one-off deploy buys **~30 epochs** of Walrus storage (~1 month at testnet's ~1-day epochs). The site is live at its URL for that window; after it, the storage lapses unless you **extend** it (stage 2) or hold a **subscription** that auto-renews it (stage 3). The exact end is on-chain — the binding blob's `storage.end_epoch`, returned as `storageEndEpoch` / `expiresAtMs` on `GET /sites/`. Nothing is deleted server-side; permanence is the storage window you keep funded. ## 2. Extend storage — $0.50 A deploy covers the site plus an initial storage period. To keep a site alive past it, extend its storage: `POST /sites//extend` with no payment → **402** for the renewal amount → build + sign the payment → retry with `X-PAYMENT` (the signed payload). The payer must be the site owner — a stranger cannot extend (and pay for) your site; the backend settles the payment only after confirming the recovered payer == the on-chain `Site.owner`. Each extend pushes the storage window out by one period. ## 3. Subscribe — one plan per account, every site covered Deploy is itself a merchant on the Suize rail, and the recurring half of that rail is an on-chain subscription. A subscription is a standalone `Subscription` object **you own** (the merchant on it is the Deploy treasury). It is **$19.99/mo** (USDC), and it is **per-ACCOUNT, not per-site**: ONE active subscription owned by your address unlocks custom domains for **all** your sites (stage 4) and auto-renews **all** their Walrus storage so nothing ever lapses. The plan is unlimited sites, with one clause — up to **100 GiB of total site storage** is auto-renewed (a generous safety ceiling on how much storage one plan keeps alive). The rail fee is carved on-chain inside each payment. Each period is one **user-signed, gasless** payment from your own key. Because the create pushes one period of USDC through the subscription module (not a plain transfer), this step is signed bytes Deploy hands you — not the bare x402 spine. It is **two requests**: build the signable bytes, then submit your signature. 1. `POST /deploy/subscribe/build { siteId, sender }` → `{ bytes, digest, siteId, merchant, amount, periodMs }`. `sender` must be a site owner; `amount` is the per-period amount (atomic USDC, 6 dp), `periodMs` the renewal window, `merchant` the Deploy treasury the periods pay. 2. Sign `bytes` **with your own key** (locally), then `POST /deploy/subscribe/submit { digest, signature }` → `{ digest, subscriptionId, siteId, active }`. The subscription is live the moment `active` is `true`; the first period is paid inline. One active plan per account covers every site you own — you do not subscribe each site separately. Renewals are user-signed and gasless, pushing exactly one period — nothing reaches into your funds. **Cancel = deleting your `Subscription` object on-chain**; Deploy sees the cancel from the rail and stops auto-renewing all your sites. Read your subscription state any time from `GET /sites/` — each of your sites carries a `subscribed` flag reflecting your account's active plan. ## 4. Add a custom domain — DNS-verified Point your own domain at a deployed site. **Requires an active Deploy subscription on your account** (stage 3) — one plan unlocks custom domains for every site you own; without an active plan the calls below answer **402**. This is a two-call flow with a DNS step in between that a HUMAN performs (only they can edit the domain's DNS), so an agent's job is to relay the exact records and then verify. 1. **Issue the challenge** (no auth, writes nothing): `POST /domains { siteId, domain }` → the records to add: - `txtName` — the TXT host, `_suize-verify.` (proves ownership). - `txtValue` — the TXT value (a fresh challenge token). - `cname` — the CNAME target, `.suize.site` (routes the domain). Relay `txtName` / `txtValue` / `cname` to your human; they add the TXT record and the CNAME at their DNS provider and wait for propagation. 2. **Verify + link** (owner-signed): `POST /domains?verify=1 { siteId, domain, ts, signature }`, where `ts` is the current ms-epoch timestamp and `signature` is a base64 personal-message signature over `Suize Deploy\nlink -> \n@` from the site owner's key. The backend checks the TXT (ownership) AND the CNAME (routing), recovers the signer == `Site.owner`, and accepts `ts` within **~1 hour** (no server-issued nonce — the auth is stateless), then links the domain on-chain. The owner signs ONCE: while the DNS records propagate the response is `status: "pending"` with `txtOk` / `cnameOk` flags and a `detail` naming the missing record — re-call with the SAME `ts`/signature until `status: "linked"`. (If a slow DNS zone takes longer than the hour, the owner simply re-signs with a fresh `ts`.) **Unlink** is simpler — owner-signature only, no DNS step, takes effect immediately: `DELETE /domains/` with `{ ts, signature }`, the signature over `Suize Deploy\nunlink \n@`. The backend recovers the signer == the `Site.owner` of the site the domain currently points at, then removes the link on-chain. (You may be unlinking precisely because you no longer control the domain's DNS — so unlink never asks for a DNS proof.) ## Links - Dashboard (deploy from a browser): https://deploy.suize.io - API host: https://api.suize.io - The Suize rail — how to pay anything, and the full x402 contract: https://suize.io/llms.txt - The consumer wallet (fund an AI to pay for you): https://wallet.suize.io - This file: https://deploy.suize.io/llms.txt