Reader payments and gift access
A reader can use a Nano, explicit Base SIWX or verified-email owner Bearer session. Owner Bearer/v1 purchases, tips and x402 payments retain the selected owner. First USDC purchases in the browser can use a cookie-only guest checkout, without a separate login signature; verified payment admits a payment-grade renewable owner session. Explicit owner API and native MCP buyers log in through SIWX and use an owner Bearer; standard USDC x402 buyers can use the wallet-only flow below. Anonymous website Nano checkout also recognizes the confirmed paying wallet. Publishing keys cannot buy, tip, claim or manage gift links. Signing happens in the reader's wallet; Subnano never receives a seed or private key. 1 XNO = 10^30 raw; 1 USDC = 10^6 micro-units. Keep every amount as an exact integer string in its own asset/network.
Purchase agreement, delivery consent and confirmation
GET /api/legal/purchase-terms returns the current version, full Terms, consent text and public marketplace operator. Every new original Purchase saves its complete agreement, original creator beneficiary, asset/network, integer price and commission. Later Terms, Post edits, guest promotion and recovery cannot rewrite it. The creator is the seller; Subnano is the marketplace. An order confirmation is not a VAT invoice or verified creator legal identity.
For express buyer consent to immediate digital delivery and acknowledgement of losing the applicable withdrawal right when delivery begins, send X-Subnano-Purchase-Consent: <original terms version> when issuing the quote or preparing its original signing guard. General login Terms, wallet connection, a spending grant or payment signature do not provide this consent. A stale version conflicts; review the original agreement rather than today's Terms for an old operation. Consent cannot be added after submission/payment.
The custom header is optional for ordinary owner-Bearer API/x402 clients. Stock @x402/fetch can still pay without a separate legal handshake; Subnano does not claim loss of withdrawal rights where valid consent/confirmation is missing. The optional subnano-purchase-contract extension uses x402's info/schema shape to advertise the original purchase ID, version, SHA-256 and document location. It does not change accepts, EIP-3009 or financial Payment-Response. It is Subnano metadata, not the official signed Offer and Receipt extension; a payment signature does not cryptographically sign the Terms.
Browser checkout uses an unchecked per-purchase choice. MCP stops before signing until the buyer expressly authorizes accept_immediate_delivery: true. Neither adds a wallet login signature. Original recovery, signed operations and previous purchased reads never require new consent or an enabled issuance gate.
GET /api/v1/purchases/<original purchaseId>/receipt
Authorization: Bearer <original ownerAccessToken>
JSON returns documentText/sha256, termsVersion, consentText, consentAcceptedAt and, once canonically paid, receiptText/receiptSha256. Save those exact UTF-8 bytes. Hashes detect changed bytes; they are not server signatures. ?format=pdf returns an inline application/pdf containing the original financial details, consent and full saved Terms, with the exact UTF-8 records embedded. View receipt opens it and Save receipt downloads it after an explicit click, both after purchase and under Settings → Purchases. No file is downloaded automatically during payment or recovery. The legacy ?format=html attachment remains available. MCP saves the structured documents in its existing private operation store and returns them through purchase status. A current Terms link alone is not a confirmation; availability of a receipt is not evidence that a buyer retained a copy.
Receipt access uses existing original-owner/private-browser-checkout authority. Public hashes, copied proofs/documents or order IDs cannot grant access. Historical paid orders without evidenced Terms return 404 rather than fabricated consent; content and payment recovery still work. Legal copy retrieval can retry independently of the paid order and never creates another charge.
For questions, complaints or an applicable withdrawal request, contact hello@subnano.me with the original order ID. There is no in-app withdrawal/refund workflow. Cookie writes require exact website origin. Documents remain available during an issuance pause.
Native purchase sessions
This family issues Nano sessions. For USDC use the Base x402 flow or guest checkout below.
POST /api/v1/purchases/create
Authorization: Bearer <ownerAccessToken>
Idempotency-Key: <persisted request identity>
Content-Type: application/json
{"postId":"<published Post UUID>"}
Success returns {purchase,statusUrl,ephemeral_address:{id,ephemeral_address},reused}. Save the native purchase ID and quote before paying. New Nano allocations last five minutes, including converted offers on USDC-anchored Posts; originals retain their saved expiry. The purchase projection includes native amount/status, expiry, buyer, incoming/receive hashes and safe settlement metadata; it excludes private financial snapshots. Exact final amount_paid_raw appears when confirmed. statusUrl is the existing /api/purchases/:purchaseId, which accepts the same owner session.
V1 issuance requires an Idempotency-Key of 1–255 trimmed nonwhitespace characters. The native request identity and payment allocation commit together. An exact replay returns the same persisted allocation and re-registers its monitor address even after an earlier registration response was lost. Reuse with different intent returns 409; do not issue another payment after an uncertain response. Existing pending allocations also retain their original amount/routing. New allocations require actual-beneficiary receiving readiness and payment monitor availability.
GET /api/v1/purchases/:purchaseId reads only the owned purchase. POST .../:purchaseId/cancel cancels a pending session and returns {message,purchase:{id,status}}. It retains allocated context and monitoring because already broadcast funds can arrive later. POST .../:purchaseId/claim on a paid/swept registered-owned purchase returns {session:null,purchase}; it cannot switch to another wallet-recognized identity.
Purchase status=paid establishes the entitlement; receive_status and receive_block_hash track platform receipt. Creator earnings/payout are read separately. Never charge again because payout is blocked or the creator's wallet has not received its send.
The anonymous website uses /api/purchases/session with {purchaseId} and its original checkout cookie. Confirmed payer evidence can promote that guest or select an existing Nano account, returning a browser wallet projection and an HttpOnly refresh cookie. Sharing the QR deliberately permits that payer account to become the checkout browser owner. Explicit Bearer/v1 callers retain their selected identity and receive session:null. Owner agents can complete the entire normal journey through the v1 family.
Post and Comment tips
Tips remain Nano-only. A Base-only creator without Nano payout setup cannot receive Nano tips.
| Action | Post path | Comment path |
|---|---|---|
| POST issue | /api/v1/posts/:postId/tips/session | /api/v1/posts/:postId/comments/:commentId/tips/session |
| GET state | .../tips/:sessionId | .../tips/:sessionId |
| PATCH cancel | .../tips/:sessionId | .../tips/:sessionId |
| POST claim | .../tips/session-claim | .../tips/session-claim |
Issue uses the owner and a required persisted request key. Post body is {amountNano,amountMode?}: preset is default, positive XNO rounded to six decimal places with at least 0.000001 XNO; custom stores no fixed suggested amount and may omit amountNano. Comment body is {amountNano,recipientKind?} with post_author default or comment_author. Tipping the Post author through a Comment requires that you authored that Comment; tipping the Comment author cannot tip yourself or a deleted Comment. Existing access/visibility restrictions remain.
A new session checks the actual beneficiary's saved address. A ready Comment author can receive a tip even when an unrelated legacy Post author needs setup. Replays recover the original session before today's address/publication check; they never reroute an allocation.
Issue/read returns {session,...}; the complete session is {id,status,postId,commentId?,statusUrl,tipperProfileId,authorProfileId,tipRecipientKind?,amountMode?,suggestedAmountRaw,ephemeralAddress,amountReceivedRaw,authorAmountRaw,platformFeeRaw,expiresAt,paidAt,cancelledAt,expiredAt,receiveBlockHash,authorTransferHash,platformTransferHash,meta}. Amounts/hashes/times remain null when unproven. The canonical status URL is the existing unversioned tip read path. paidAt is incoming tip acceptance, not creator send completion; author/commission transfer hashes and owner earnings show the separate legs.
meta.receipt_continuation_completed=false means incoming money is accepted while receipt activity is still being completed or retried. Keep polling the same status URL until it becomes true, then inspect or claim that same checkout receipt. Never send again because this continuation is pending. Legacy receipts may omit the marker.
Cancel PATCH accepts {status:"cancelled"} and returns {session:{id,status,cancelledAt}}; noncancellable/foreign session returns 404. Late payments remain accounted after cancel/expiry. Claim POST accepts {tipId}; a paid registered-owned tip returns {session:null,tip}. For browser cookies, the canonical anonymous .../tips/session-claim atomically logs the browser into the confirmed paying wallet and returns a browser wallet projection; its refresh credential stays in an HttpOnly cookie. Sharing a QR therefore allows the payer account to become the browser owner. An explicit Bearer/v1 caller retains its own account and receives session:null. No caller can provide a target owner ID.
x402 protected reads and assets
Use the existing public GET /.well-known/x402, /.well-known/x402/posts, /.well-known/railhint.json and /llms.txt. Browse GET /api/posts first. OPTIONS /api/posts/:postId/access and /api/assets/:assetId describe payment-header/CORS behavior. These discovery routes require no Publishing key.
GET /api/posts/:postId/access returns free/owned/entitled content or HTTP 402 plus base64 JSON Payment-Required. Inspect scheme exact, network nano:mainnet, asset XNO, raw amount, destination, resource and expiry against your wallet policy before signing. A registered owner's first valid payment proof is evaluated under that same owner even before entitlement exists. A quote tied to another caller is rejected before broadcast.
The Payment-Signature JSON uses x402Version:2, the original resource, the exact accepted offer and payload.block. Set block.link to the accepted payTo address's 64-hex public key, as in a canonical signed Nano state block. The server derives the destination from this signed field. block.link_as_account is optional; if supplied, it must encode the same destination (either nano_ or xrb_ prefix). A conflicting or malformed address is rejected. Do not put an address in block.link: SDKs may accept an address while constructing a block, but the signed wire block must contain hexadecimal link. Signature, work, exact amount, original quote and confirmed replay checks still apply. See the exact payload schema and Nano block specification.
Retry the exact resource with base64 JSON Payment-Signature; X-Payment is an identical compatibility alias. Read post.content.paid.markdown or .tiptap; protected /api/assets/:assetId belonging to the same Post accepts the same settled proof, without another purchase. Decode Payment-Response and compare success, transaction, payer, amount and network with the accepted quote. Read failures do not prove a send was not broadcast: replay the same proof/reconcile the wallet frontier before signing again. An expired unbroadcast quote can be re-quoted under the same spending policy. Server invalid work/proof returns 400 and must not broadcast.
Existing owner /api/posts/:postId/purchased-content and /api/posts/by-identifier/:handle/:postId/purchased-content expose the same entitlement rather than a separate purchase engine. Public Profile/summary/activity/follow reads stay on canonical discovery paths listed in overview.
Private ZIP attachments expose ready-file metadata before payment. ZIP 402 responses quote the original /api/posts/:postId/access resource: settle that endpoint first, then replay the identical proof for all matching files. An unsettled asset proof returns 403 post-payment-unsettled and performs no settlement. Verify each file's SHA-256 and length. After withdrawal, existing native buyers and already-settled proofs retain saved ZIP downloads; free/gift access and new purchases close. The runnable ZIP example covers creator recovery and purchase-once/download.
Base USDC purchases
A first stateless x402 purchase needs no Subnano login or account credential. GET /api/posts/:postId/access?rail=usdc with an ordinary x402 client, inspect the 402 quote, sign the native USDC authorization locally and resubmit Payment-Signature to that same resource. Neither cookies, a custom checkout header, an Idempotency-Key nor a prepare-signing call is mandatory. An optional request key replays the initial quote within its original IP/resource scope. Save the original quote and exact signed proof before submission; after uncertainty, retry that same proof rather than creating another payment. Confirmed anonymous proofs replay only their original Post; later Post/ZIP access can use the resource-only SIWX extension below. No account session or cookie is issued by this payment.
USDC quotes include the standard extensions.bazaar declaration for GET /api/posts/:postId/access: query rail=usdc, a concrete public Post ID, and a JSON output schema for post.content.paid.markdown. routeTemplate consolidates concrete Post URLs into one endpoint catalog entry. Service name, description and topical tags are server-declared; buyer-supplied metadata cannot change the declaration sent to the facilitator. Worker retries rebuild it from the same frozen Post/resource. A Bazaar-capable facilitator catalogs the endpoint after successful payment; deployment and a real catalog lookup must verify the live listing. No private account/asset route, credentials or actual paid content are included in the discovery example.
A pending anonymous submission returns 409 usdc-settlement-pending with retryUrl; replay the exact original Payment-Signature there. Explicit owner submissions retain their owned statusUrl. If the signature was saved before quote expiry but a provider/request failure prevented the first disclosure checkpoint, the same held anonymous authorization can obtain that checkpoint until its original validBefore. No new signature, quote extension or account login is needed.
For an authenticated purchase, use an existing owner session or obtain one through explicit SIWX login. The wallet paying USDC must be a proved Base identity of that same owner. Explicit owner API purchases retain their selected owner; stateless first purchases use the wallet-only flow above. Login, payment authorization and returning resource-read proof are distinct. The browser adapter uses the same owner, original purchase and entitlement as this Bearer API. Browser availability depends on the separate admission and new-issuance gates; payment remains EOA-only.
GET /api/posts/<postId>/access?rail=usdc
Authorization: Bearer <owner accessToken>
Idempotency-Key: <saved purchase request identity>
An eligible new purchase returns 402 with Payment-Required: x402 v2 exact, network:"eip155:8453", native Circle token 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, integer micro-unit amount, frozen payTo, resource and extra:{name:"USD Coin",version:"2",quoteId}. New quotes last five minutes (300 seconds). Browser and MCP prepared guards fix EIP-3009 validBefore to that quote expiry. The stock @x402/evm 2.28.0 exact client uses its actual signing time plus 300 seconds and does not require a prepare-signing call. Retain that original signed deadline for recovery; older originals keep their saved validity. Enforce the exact network/token, maximum budget, destination, resource and expiry before signing. Discovery prices are indicative; the saved quote is payable truth. Failed reference-rate refreshes retain the last good rates and provider timestamps; converted offers require both timestamps at most 60 minutes old, with operator incidents after 30 minutes. New USDC offers require 0.25 USDC when the Post has a pending or ready ZIP, and 0.10 USDC for text. This includes USDC amounts derived from an XNO anchor; Nano has no monetary floor. The offer’s minimumAtomic and reason explain an unavailable rail. Removing all pending and ready ZIPs restores the text minimum. Existing quotes, purchases, ZIP reservations and financial snapshots keep their original terms. Off, stale conversion, below-floor or missing payout setup does not silently raise a creator's price.
Use the installed @x402/core, @x402/evm exact client and @x402/fetch with the same owner's Bearer and stable Idempotency-Key. Sign EIP-3009 TransferWithAuthorization locally. Save the original quote ID and exact base64 JSON Payment-Signature before sending it to the quoted Post endpoint. The payer, native-token EIP-712 domain, amount, authorization nonce and saved quote must match. The server persists the original authorization before facilitator settlement. The stock SIWX hook can try resource-read proof before payment; it cannot establish owner login for you.
Successful incoming receipt verification returns the Post and Payment-Response. Inspect the original /api/purchases/<quoteId> with the same owner Bearer. Buyer status:"paid" grants Post/attachment access independently of payout.state. A 0.10 USDC sale owes the creator 85,000 micro-units; Subnano's 15% commission pays its own operating costs. Creator amounts round up to the next micro-unit where necessary and the platform receives the exact remainder.
409 usdc-settlement-pending supplies purchaseId, original statusUrl and retry:"original"; retry that status or the identical authorization. Provider timeout, a lost reply, quote expiry or Post withdrawal can still lead to original receipt acceptance. Never sign another payment while the original may have succeeded. A hash is a receipt candidate until the two configured RPCs agree on sealed canonical inclusion and exact token/authorization evidence. Safe and finalized progression are separate. 410 usdc-quote-expired means an unclaimed quote expired or the original authorization was safely closed; deliberately obtain a new quote with a new Idempotency-Key under the same spending policy. A closed status has settlement.state:"expired" and retry:null. Merely reaching the quote deadline, or seeing no transaction hash, does not establish safe closure.
Attempt closure depends on what Subnano holds. An unprepared quote closes at expiry. A prepared guard whose signature Subnano never received closes from database facts at quote expiry plus 60 seconds; choosing Nano closes either of these unsigned tiers immediately and proceeds. A held or forwarded signature closes only after both configured RPCs prove it unused at an agreed finalized block beyond its original validBefore plus the 60-second margin, or after receipt acceptance. Subnano checkpoints the observation block before facilitator verification or settlement. Quote expiry alone never closes a held signature. Provider absence or disagreement cannot prove nonpayment or a reorganization; the closed original remains recorded.
USDC_NEW_ISSUANCE_ENABLED controls new USDC quotes (503 usdc-issuance-disabled); USDC_BROWSER_ADMISSION_ENABLED independently controls browser login/link and eligibility for a new browser checkout. Both default on; an explicit false pauses the corresponding capability. Missing receiving address, either Base RPC URL or facilitator credentials forces both gates off. Valid provider/payment configuration and the database rollout are still required. Original saved recovery, creator payouts and purchased reads remain available to the existing owner even when both are off. Base Sepolia is an isolated validation configuration: network eip155:84532, token 0x036CbD53842c5426634e7929541eC2318f3dCF7e, EIP-712 name USDC, version 2. Never reuse mainnet terms/signatures for it.
Guest USDC checkout
The browser retains cookie-only guest checkout. A verified payment resolves the network-qualified paying wallet and saves the purchase to its existing or newly admitted account without a separate login signature. An already selected durable owner keeps that account; payment cannot link a wallet or merge accounts.
Same-origin POST /api/usdc/guest-checkout with {postId} installs an HttpOnly checkout cookie. The browser quotes, prepares, submits and recovers through same-origin POST /api/posts/:postId/usdc-checkout. Acceptance installs the HttpOnly wallet-refresh cookie and returns only the browser session projection, never a refresh secret. Source-bound session claim/recovery has five-minute windows; sessionRecovery:"login-required" means log in with the same wallet to read the saved purchase, without paying again.
Browser guest credentials remain cookie-only. Stateless API purchases use the ordinary x402 exact scheme and a server-private checkout binding, with no custom guest header or headless session pair. The native MCP buyer retains explicit owner login. Explicit SIWX login and ordinary owner refresh still return their documented private token pairs. A copied proof, wallet address or transaction hash cannot claim another checkout's account.
Payment-derived owner sessions support reading, purchasing, history, publishing and account settings without another wallet confirmation. Mutations retain active owner/session checks, new-wallet signatures, mailbox verification and deletion freshness/handle checks. See session assurance.
Browser checkout and interrupted signing
Browser cookie owners and private guest checkouts use same-origin POST /api/posts/:postId/usdc-checkout with action:"lookup", "quote", "prepare-signing" or "submit". Quote uses a saved Idempotency-Key; prepare supplies only the original purchase ID and payer. An existing owner needs a proved linked payer; a guest proves the payer through the verified payment. Submit supplies only that ID and the 65-byte payment signature. The server derives the canonical GET resource and reconstructs the exact original authorization. Explicit invalid Bearers never fall back to a browser cookie, and legacy cookie-authorized GET paths cannot allocate or settle payments. Owned GET reads remain available.
Before the browser requests a spendable signature, Subnano saves the exact non-spendable payer, token/network, nonce, amount, destination and validity in the existing original incoming operation. A lost preparation response, wallet callback, reload or missing local storage therefore retains the same purchase and terms. Owner/Post lookup finds the selected Nano or USDC purchase on another device. A pending Nano selection prevents switching to a new USDC intent.
Wallet connection and rail selection do not prepare a guard; preparation happens only on explicit Pay. The wallet prompt waits until the original authorization deadline. An unsigned guard can close from database facts at quote expiry plus 60 seconds, or immediately when choosing Nano. Restart applies the same unsigned closure rule and allocates nothing. Once Subnano holds the signature, only receipt acceptance or paired finalized unused proof beyond its original validity plus margin permits closure. Browser storage holds correlation metadata only; it does not store payment signatures. Login, connection and payment are separate approvals, and rejecting payment does not log the owner out.
Returning owner and SIWX reads
After a confirmed purchase, use the owner Bearer for the Post and every saved attachment URL. No new payment proof or charge is needed. Protected asset routes cannot submit or settle a USDC purchase; return to the original Post resource to recover payment. After withdrawal, the existing buyer and author retain saved content/ZIP access while free/gift access and new purchases close.
Without a current owner token, request the exact saved read URI with siwx=read added, preserving its other query parameters. 401 with data.code:"siwx-read-required" contains data.extensions["sign-in-with-x"] (info, supportedChains, schema). Generate a stock createSIWxPayload/encodeSIWxHeader EIP-191 proof for that exact URI and retry it with the Sign-In-With-X header. Its nonce lasts five minutes and is consumed once; a failed/lost content response needs a fresh challenge. This proves the entitled wallet for that resource and creates no account session or cookie. Invalid explicit Bearers fail even alongside a valid read proof. Public transaction hashes and published payment authorizations cannot retrieve private bytes.
Legitimate repeat reads remain free. Process-local burst limits are 120 deliveries per IP and 60 per owner per minute; honor 429 and Retry-After. These limits reset across instances and do not represent a lifetime quota or a global cost cap. Verify every ZIP's saved SHA-256 and length.
Author Gift Links
The owner of a Post can GET, POST or DELETE /api/v1/posts/:postId/gift-link. POST requires a published paywalled Post and gets or creates one saved active link; repeated POST returns the same token. GET returns {status,active,eligible,url,createdAt,disabledAt} with none, disabled, active or dormant. A saved link is dormant while its Post is ineligible. POST active result omits eligible. DELETE returns {status,active:false,disabledAt}.
Read protected content through /api/posts/:postId/access?gift=<token>; pass the same grant to allowed assets/identifier content routes. Gift access is temporary, resource-bound and revocable; it does not create a permanent purchase entitlement. Disabled/dormant/foreign tokens cannot grant access. Tokens/URLs are secrets: these responses use private no-store, and clients must exclude them from telemetry.
Financial completion
Incoming acceptance, platform receive, reader entitlement, creator confirmed send and creator spendability are different outcomes. Owner financial history shows every accepted source even if no outgoing row exists. Prepared signed blocks and expected hashes support safe same-block recovery; paid proves a creator send confirmation, not the creator's receive. Local fault tests mock Nano; documentation and local HTTP tests alone are not real-chain settlement proof.
Browser continuation across official and retained hosts
When the authenticated owner encounters an unresolved native Purchase on the other application host, browser checkout shows safe status and an explicit continuation to the original host. That encounter does not restart, expire, reconcile, sign, settle or replace the Purchase. Authenticate independently on the original host and continue its exact saved operation. A guest keeps only the original browser and host capability; public branding does not transfer it. Existing receipts, resources, quote terms and signed authorizations retain their original destinations.