# Buy storage, pay as another wallet, and recover Read the [common rules](README.md) and obtain a [session credential](session.md). Nothing here pays by itself. While purchases are off for your network, `pay_url` is empty, a quote shows no price, and these routes refuse without a challenge. Funded runs are opt-in and are not part of these recipes. Read the terms from each response; the values below are placeholders. ## Terms | Term | Meaning | | --- | --- | | Capacity | Total storage including stored history, from 100 MB through 10 TB in exact 100 MB steps. MB, GB and TB are decimal: 1,000,000, 1,000,000,000 and 1,000,000,000,000 bytes. | | Month | 30 days (2,592,000 seconds). | | Price | $0.50 per GB-month, charged in the network token named by the quote. The quote shows the exact integer `amount` in token base units. Read `asset_decimals`; never use floating point. | | Minimum fit | The smallest capacity that holds what you store plus reserved bytes plus the refused write, rounded up to 100 MB. A smaller request is refused before any charge. Per-Kit sizes are in `GET /v1/storage`. | | Months | `months_to_add` is a whole number. With no active plan it must be at least 1. With `0` months you only raise capacity, and only while your plan has at least 30 minutes left. | | Quote end | The earlier of 30 minutes after creation and your plan's next expiry or restore cutoff. After that time the quote cannot be paid. | | No refunds | There is no cash refund. A payment that arrives late, after a newer quote, or twice is turned into storage time automatically and the result says so. | | Network | `chain_id`, `asset` and the recipient come from the quote. Pay only the quoted network and token. Old-network purchases are lookup-only. | | Retained bytes | Deleted bytes stay counted until reclaimed. Writes are refused when retained bytes exceed three times the capacity basis, even when billed bytes fit. Buying more capacity raises the basis. | | Lifecycle | Active until `expires_at`. Writes stop at expiry. Grace lasts 30 days after expiry. Suspended follows until the restore cutoff, 60 days after expiry. Paying before the cutoff restores undeleted Kits and their history. Paying after it starts a new empty term. | | Upload limits | Capacity never raises limits. Each hosted upload is at most 1 GiB and 3,000 files, and a remix source at most 1 GiB. Read `GET /v1/capabilities`. | ## Check storage and promo ```http GET /v1/storage HTTP/1.1 Host: mpp.uno.fun Authorization: Bearer PUBLIC_TEST_TOKEN_NOT_A_CREDENTIAL ``` `data.storage.plan` holds the `state` (`AGENT_PLAN_STATE_NO_PLAN`, `..._ACTIVE`, `..._LAPSED`, `..._GRACE`, `..._SUSPENDED`, `..._GONE`), `capacity_bytes`, `expires_at`, `grace_ends_at` and `restore_cutoff_at`. `data.storage` also holds `used_bytes`, `reserved_bytes`, `retained_deleted_bytes`, `minimum_fit_bytes` and `kit_sizes`, which show what fits, and `pricing`, which states the current unit price and whether offers are enabled. A new Agent account may receive one promo grant on its first successful private write while campaign slots remain; if it was granted, `data.storage.promo_grant` keeps its original capacity and expiry even after later purchases. `data.purchases[]` lists open and paid purchases, each an owner purchase with `purchase.purchase_id` and `pay_url`, so a fresh runtime with only the account key finds them. Follow `data.next_purchase_cursor` (query `purchase_cursor`) and `data.storage.kit_sizes.next_cursor` (query `kit_cursor`) for more pages. ## Get a quote ```http POST /v1/purchases HTTP/1.1 Host: mpp.uno.fun Authorization: Bearer PUBLIC_TEST_TOKEN_NOT_A_CREDENTIAL Content-Type: application/json Idempotency-Key: public-test-buy-1 {"capacity_bytes":"1000000000","months_to_add":"1"} ``` Both numbers are decimal strings. Keep the key and the exact body. The first call returns HTTP 402 with a `WWW-Authenticate: Payment` header, which is the payment challenge. In the envelope body, the `details` entry of type `uno.kit.v1.PaymentChallenge` holds the challenge (`amount` in whole tokens, `currency`, `pay_url` and `offers`), `data.purchase` holds the frozen quote (`quote.amount` in token base units, `quote.chain_id`, `quote.asset`, `quote.capacity_bytes`, `quote.months_to_add`, `quote.usd_per_gb_month`, `quote.expires_at`, `quote.no_refund_statement`), and `next_action` points at the pay URL. Compare the amount, token, chain and capacity with what you intended before paying. Store `data.purchase.purchase_id`. If the purchase is already paid, the same call returns HTTP 200 with the immutable result (in `data.purchase.purchase.results`) instead of a new challenge. If a payment is in flight it returns pending guidance with a poll `next_action`, never a second challenge. ## Pay Pay with an MPP-capable wallet. The Tempo CLI takes the exact `pay_url` and the quoted network, amount and token, and pays and retries in one step: ```sh tempo request --network testnet --payment-intent charge \ --max-spend 0.5 --payment-token 0xPUBLIC_TEST_TOKEN \ -X POST 'https://mpp.uno.fun/v1/purchases/PUBLIC_TEST_LINK/pay' ``` The Uno CLI and MCP print this command with an extra `-H 'x-uno-expect-account: '` header. On an anonymous payer call the pin is checked against the link's beneficiary. A wrong pin is refused with HTTP 400 `failed_precondition` / `ACCOUNT_MISMATCH` before any charge; stop and check the link. Use `--network mainnet` only when the quote's `chain_id` is the mainnet id. `--max-spend` is the quoted amount in whole tokens. Another wallet may instead send the `Payment` retry itself; it targets the same URL as the quote request, carries no Bearer, and repeats the same body: ```http POST /v1/purchases HTTP/1.1 Host: mpp.uno.fun Authorization: Payment PUBLIC_TEST_PAYMENT_NOT_A_CREDENTIAL Content-Type: application/json Idempotency-Key: public-test-buy-1 {"capacity_bytes":"1000000000","months_to_add":"1"} ``` The purchase and quote come from the verified challenge, not from your body. A success is HTTP 200 with a `Payment-Receipt` header. `data.purchase.status` is `PURCHASE_STATUS_SETTLED` and `data.purchase.results` holds the applied outcome: `classification` (`quoted`, `stale`, `late` or `extra`), `applied_amount`, `capacity_bytes`, `starts_at`, `expires_at` and `payment_tx`. Settlement time is when the writer applied the payment, not the chain time. Never report storage as bought before `PURCHASE_STATUS_SETTLED`. ## Pay as a separate payer The owner sends the payer only the `pay_url` from the quote. The link names the beneficiary account and grants payment only: it gives no owner access, and the payer view shows terms, status and receipts without usage, Kit sizes or credentials. The payer needs no account of its own and sends no Bearer. ```http POST /v1/purchases/{link}/pay HTTP/1.1 Host: mpp.uno.fun Authorization: Payment PUBLIC_TEST_PAYMENT_NOT_A_CREDENTIAL Content-Type: application/json {} ``` Without a `Payment` credential this request returns the 402 challenge and moves no money. The owner stays the acting account whoever pays, and any extra or late transfer still becomes storage time for the beneficiary. To confirm whom you fund before paying, ask the owner for their account id along with the `pay_url` (the payer view omits it) and add `x-uno-expect-account: `. A pin that does not match the link's beneficiary gets HTTP 400 `failed_precondition` / `ACCOUNT_MISMATCH` before any charge. ## Look up and recover Reading never pays. Check before you pay again after any lost response, crash or new runtime. ```http GET /v1/purchases/{id} HTTP/1.1 Host: mpp.uno.fun Authorization: Bearer PUBLIC_TEST_TOKEN_NOT_A_CREDENTIAL ``` This owner read returns the stored purchase in `data.owner.purchase` with its `quote`, `status` and `results`. `GET /v1/storage` lists the same purchases from the account key alone. A payer with only the link polls without a credential: ```http GET /v1/purchases/{link}/pay HTTP/1.1 Host: mpp.uno.fun ``` This reconciles a pending payment against the network without broadcasting anything and returns `data.purchase`. Honor `next_action.delay_seconds` and the `Retry-After` header between polls. | What you see | What to do | | --- | --- | | Lost response to the quote | Replay `POST /v1/purchases` with the same key and body. You get the same purchase and quote, or the recorded result. Never use a new key to recover. | | Quote ended before payment | Replay the same key and body. After the quote end you get a new quote under the same purchase; earlier quotes stay readable. The replay never pays. | | Pending | Poll `GET /v1/purchases/{link}/pay`. Never pay again while a payment may be in flight. Pending attempts fence only their own purchase and never block another purchase. | | Settled | Read `results`. A replay returns the same result without a second charge or time grant. | | Different capacity or months under a used key | The request is refused as `invalid_argument`. Use a new key for a new purchase. | | Expected-account mismatch | Stop. The call was pinned to a different account than the link or session names. | After settlement, retry the write that needed storage with its original `Idempotency-Key` and exact body. A write never takes payment, and a write retry and a purchase retry are separate requests.