{"openapi":"3.1.0","info":{"title":"VpsAgent","version":"0.1.0","summary":"Disposable VPS instances for AI agents — paid via x402 (USDC on Base).","description":"Two product lines: ephemeral sandboxes (1h / 6h / 24h, $0.50–$5.00) for code execution, CI runs, browser automation; persistent servers (small / medium / large, 30-day or 365-day, $14.99–$449.99) for hosting agents and services. Pay-on-success: USDC settles only when the box is reachable on TCP :22. Failed provisions never settle — the EIP-3009 authorization expires unused, agent pays nothing. No signup, no API keys, machine-to-machine.","license":{"name":"Proprietary"},"contact":{"name":"VpsAgent","url":"https://vps.phoneagent.xyz/feedback"}},"servers":[{"url":"https://vps.phoneagent.xyz"}],"x-x402":{"scheme":"exact","network":"eip155:8453","asset":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913","payTo":"0x8eCbd65228930060363C70854da7D37D57ffC358"},"paths":{"/catalog":{"get":{"operationId":"listCatalog","summary":"List rentable SKUs across both tracks.","description":"Live storefront. Returns nine SKUs across two tracks (sandbox + server) with prices, spec floors, durations, and Phase-1 availability flags. Paid discovery: 0.01 USDC per call, settled inline. The dust price exists to register the resource in CDP Bazaar's discovery index.","responses":{"200":{"description":"OK","headers":{"X-Payment-Response":{"description":"Base64-encoded `{tx, network}` of the on-chain settlement.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogList"},"examples":{"live":{"summary":"Every SKU, priced, as served today.","value":[{"sku":"sandbox_1h","track":"sandbox","price_usdc":0.5,"duration_minutes":60,"spec_floor":{"vcpu":1,"memory_mb":1024},"available":false,"description":"1 vCPU / 1 GB / 25 GB SSD ephemeral sandbox, destroyed after 1 hour. Code execution, CI runs, browser automation.","disk":{"size_gb":25,"type":"SSD"}},{"sku":"sandbox_6h","track":"sandbox","price_usdc":2,"duration_minutes":360,"spec_floor":{"vcpu":1,"memory_mb":1024},"available":false,"description":"1 vCPU / 1 GB / 25 GB SSD ephemeral sandbox, destroyed after 6 hours.","disk":{"size_gb":25,"type":"SSD"}},{"sku":"sandbox_24h","track":"sandbox","price_usdc":5,"duration_minutes":1440,"spec_floor":{"vcpu":1,"memory_mb":1024},"available":false,"description":"1 vCPU / 1 GB / 25 GB SSD ephemeral sandbox, destroyed after 24 hours.","disk":{"size_gb":25,"type":"SSD"}},{"sku":"server_small_1mo","track":"server","price_usdc":14.99,"duration_minutes":43200,"spec_floor":{"vcpu":4,"memory_mb":8192},"available":true,"description":"4 vCPU / 8 GB / 100 GB SSD / 200 Mbit/s persistent server, 30 days. Renewable.","disk":{"size_gb":100,"type":"SSD"},"network":{"port_mbits":200,"traffic":"unmetered"},"region_pricing":{"default":"EU","surcharge_usd":{"EU":0,"US-central":3,"US-east":3,"US-west":3,"SIN":6,"UK":3,"AUS":6,"JPN":6,"IND":6}}},{"sku":"server_small_12mo","track":"server","price_usdc":149.99,"duration_minutes":525600,"spec_floor":{"vcpu":4,"memory_mb":8192},"available":true,"description":"4 vCPU / 8 GB / 100 GB SSD / 200 Mbit/s persistent server, 365 days. ~20% off the monthly rate.","disk":{"size_gb":100,"type":"SSD"},"network":{"port_mbits":200,"traffic":"unmetered"},"region_pricing":{"default":"EU","surcharge_usd":{"EU":0,"US-central":36,"US-east":36,"US-west":36,"SIN":72,"UK":36,"AUS":72,"JPN":72,"IND":72}}},{"sku":"server_medium_1mo","track":"server","price_usdc":22.99,"duration_minutes":43200,"spec_floor":{"vcpu":6,"memory_mb":12288},"available":true,"description":"6 vCPU / 12 GB / 200 GB SSD / 300 Mbit/s persistent server, 30 days. Renewable.","disk":{"size_gb":200,"type":"SSD"},"network":{"port_mbits":300,"traffic":"unmetered"},"region_pricing":{"default":"EU","surcharge_usd":{"EU":0,"US-central":4.5,"US-east":4.5,"US-west":4.5,"SIN":9,"UK":4.5,"AUS":9,"JPN":9,"IND":9}}},{"sku":"server_medium_12mo","track":"server","price_usdc":229.99,"duration_minutes":525600,"spec_floor":{"vcpu":6,"memory_mb":12288},"available":true,"description":"6 vCPU / 12 GB / 200 GB SSD / 300 Mbit/s persistent server, 365 days. ~25% off the monthly rate.","disk":{"size_gb":200,"type":"SSD"},"network":{"port_mbits":300,"traffic":"unmetered"},"region_pricing":{"default":"EU","surcharge_usd":{"EU":0,"US-central":54,"US-east":54,"US-west":54,"SIN":108,"UK":54,"AUS":108,"JPN":108,"IND":108}}},{"sku":"server_large_1mo","track":"server","price_usdc":44.99,"duration_minutes":43200,"spec_floor":{"vcpu":8,"memory_mb":24576},"available":true,"description":"8 vCPU / 24 GB / 300 GB SSD / 600 Mbit/s persistent server, 30 days. Renewable.","disk":{"size_gb":300,"type":"SSD"},"network":{"port_mbits":600,"traffic":"unmetered"},"region_pricing":{"default":"EU","surcharge_usd":{"EU":0,"US-central":9,"US-east":9,"US-west":9,"SIN":18,"UK":9,"AUS":18,"JPN":18,"IND":18}}},{"sku":"server_large_12mo","track":"server","price_usdc":449.99,"duration_minutes":525600,"spec_floor":{"vcpu":8,"memory_mb":24576},"available":true,"description":"8 vCPU / 24 GB / 300 GB SSD / 600 Mbit/s persistent server, 365 days. ~25% off the monthly rate.","disk":{"size_gb":300,"type":"SSD"},"network":{"port_mbits":600,"traffic":"unmetered"},"region_pricing":{"default":"EU","surcharge_usd":{"EU":0,"US-central":108,"US-east":108,"US-west":108,"SIN":216,"UK":108,"AUS":216,"JPN":216,"IND":216}}}]}}}}},"402":{"description":"Payment required. The challenge is in the JSON body AND base64'd into the `PAYMENT-REQUIRED` response header. Read the HEADER: the x402 v2 reference client (`@x402/core`) only falls back to the body when `x402Version == 1`, and throws `Invalid payment required response` otherwise. Send the signed payload back in `PAYMENT-SIGNATURE` (v2 clients do this by default); `X-Payment` is also accepted.","headers":{"PAYMENT-REQUIRED":{"description":"Base64 of the same challenge as the body. This is the header v2 clients read.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentChallenge"}}}},"422":{"description":"The payment was well-formed and understood but cannot be executed: the payer wallet's on-chain USDC balance is below the quoted price. Body is `insufficient_funds` with `balance_usdc`, `required_usdc` and `shortfall_usdc`. This is TERMINAL, not a challenge — it carries no `accepts[]`, and retrying without funding the wallet fails identically. Fund it, then retry. Every other payment failure (bad signature, spent nonce, unreadable balance) stays 402 because those may succeed on a retry."}}}},"/rent":{"post":{"operationId":"rent","summary":"Provision a VPS instance.","description":"Submit a SKU; receive a 402 with priced paymentRequirements; sign an EIP-3009 transferWithAuthorization for the SKU price; resubmit with the X-Payment header. We verify (NOT settle), provision the box upstream, and persist the rental. Worker calls /settle only when the box is reachable on TCP :22 — typically 3–5 minutes for server-track SKUs. If the box never comes up, settle is never called and the EIP-3009 authorization dies via validBefore. Agent paid nothing.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RentBody"},"examples":{"small":{"value":{"sku":"server_small_1mo"}},"large_with_region":{"value":{"sku":"server_large_1mo","region":"US-east"}}}}}},"responses":{"200":{"description":"Provisioned. Settle pending — the worker will charge once the box is up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RentalCreated"}}}},"400":{"description":"Invalid body. See the response detail for which field failed."},"402":{"description":"Payment required (or payment invalid). The challenge is returned in the JSON body AND base64'd into the `PAYMENT-REQUIRED` response header — x402 v2 clients read the header. Send the signed payload back in `PAYMENT-SIGNATURE` (or `X-Payment`; both are accepted)."},"422":{"description":"The payment was well-formed and understood but cannot be executed: the payer wallet's on-chain USDC balance is below the quoted price. Body is `insufficient_funds` with `balance_usdc`, `required_usdc` and `shortfall_usdc`. This is TERMINAL, not a challenge — it carries no `accepts[]`, and retrying without funding the wallet fails identically. Fund it, then retry. Every other payment failure (bad signature, spent nonce, unreadable balance) stays 402 because those may succeed on a retry."},"503":{"description":"SKU advertised but not yet routable in Phase 1, or upstream provider unavailable."}}}},"/rentals":{"get":{"operationId":"listMyRentals","summary":"Every rental your wallet has paid for — status, expiry, and what extending costs against your balance.","description":"Identify by wallet instead of rental_id. Unsigned, this answers 402 with an identification challenge for one base unit (0.000001 USDC) valid 60 seconds. Sign it with any x402 client and resend with PAYMENT-SIGNATURE (or X-Payment): we recover your wallet from the signature and NEVER settle the authorization, so nothing is charged. Includes expired rentals. Credentials are never returned here.","parameters":[{"name":"PAYMENT-SIGNATURE","in":"header","required":false,"schema":{"type":"string"},"description":"Signed x402 payment header for the identification challenge."}],"responses":{"200":{"description":"The wallet's rentals, newest first, each with `renew` pricing and a `next_step`."},"402":{"description":"Identification challenge (also in the base64 PAYMENT-REQUIRED header). Never settled."}}}},"/rentals/{rental_id}":{"get":{"operationId":"getRental","summary":"Public lifecycle snapshot, including the box's ipv4 once it boots.","parameters":[{"name":"rental_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RentalStatus"}}}},"404":{"description":"Rental not found."}}}},"/rentals/{rental_id}/renew":{"post":{"operationId":"renew","summary":"Extend a rental's expires_at.","description":"Token-gated. Stack cap periods ∈ [1, 12]. Same flow as /rent: 402 → sign → resubmit → verify (no settle yet). The worker clears the scheduled destruction upstream, settles, then re-locks it at the new expires_at — normally within a minute and with no human involved. Crucially: expires_at does NOT change until the renewal fully settles — if the renewal never completes, the original expires_at remains in force and the rental is destroyed at the original cancelDate. Agent paid nothing.","parameters":[{"name":"rental_id","in":"path","required":true,"schema":{"type":"string"}},{"name":"token","in":"query","required":true,"schema":{"type":"string"},"description":"root_token from POST /rent."},{"name":"periods","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":12,"default":1},"description":"How many full SKU terms to add. Price is the (sku, region) rate × periods, and the 200 echoes back the `periods` it applied — check it. Accepted here as a query parameter or in the JSON body; the query wins if both are sent. Send it AT MOST ONCE: two conflicting ?periods= values are refused with 400, never guessed between (appending &periods=12 to a URL that already carries &periods=1 is the way this happens). Out-of-range values are refused too, not clamped."}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenewBody"},"examples":{"default":{"value":{"periods":1}}}}}},"responses":{"200":{"description":"Renewal verified, settle pending.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenewalCreated"}}}},"400":{"description":"`periods` outside 1–12, not a whole number (`invalid_periods`), or sent more than once with conflicting values (`ambiguous_periods`)."},"401":{"description":"Missing or invalid token."},"402":{"description":"Payment required (or invalid). The challenge is returned in the JSON body AND base64'd into the `PAYMENT-REQUIRED` response header — x402 v2 clients read the header. Send the signed payload back in `PAYMENT-SIGNATURE` (or `X-Payment`; both are accepted)."},"404":{"description":"Rental not found."},"409":{"description":"`not_renewable`: rental not in 'active' status. Or `too_late_to_renew`: the rental ends within 15 minutes, too little runway for the extension to be in place before the box is destroyed — it would take your signature, extend nothing and leave you with no box, so it is refused while you still have time to rent a fresh one. Body carries `expires_at` and `rent_url`. Start renewals days early."},"422":{"description":"The payment was well-formed and understood but cannot be executed: the payer wallet's on-chain USDC balance is below the quoted price. Body is `insufficient_funds` with `balance_usdc`, `required_usdc` and `shortfall_usdc`. This is TERMINAL, not a challenge — it carries no `accepts[]`, and retrying without funding the wallet fails identically. Fund it, then retry. Every other payment failure (bad signature, spent nonce, unreadable balance) stays 402 because those may succeed on a retry."}}}},"/instance/{rental_id}":{"get":{"operationId":"getInstance","summary":"Live status + full SSH connection details.","description":"Token-gated. Poll after /rent until `phase == \"ready\"` (equivalently `running == true`) — a server-track box typically takes 3–5 minutes. The 200 body carries everything needed to log in: ipv4, port, user, password, and a paste-ready `connection.command`. Credentials stay retrievable here for the life of the rental, so an agent that dropped the /rent response can recover them with the rental_id + root_token.","parameters":[{"name":"rental_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"rental_id from POST /rent."},{"name":"token","in":"query","required":true,"schema":{"type":"string"},"description":"root_token from POST /rent. Not the rental_id."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstanceStatus"}}}},"401":{"description":"Missing token, or a token that doesn't match this rental."},"404":{"description":"Unknown rental id, or a rental purged ~1h after expires_at."},"502":{"description":"Upstream provider unreachable; last-known fields and valid credentials returned."}}}},"/regions":{"get":{"operationId":"listRegions","summary":"Where every region code actually puts a box. Free, no payment.","description":"Maps each `region` code accepted by POST /rent to a place, with its per-month surcharge. Read this before buying a non-default region — the codes are not self-explanatory and guessing one wrong buys a box on the wrong continent. `location` is the most precise placement we will stand behind and `precision` says what it rests on: `city` (geolocated from inside a box we delivered), `country`, or `area` (a multi-country region such as EU). `city_expected` is upstream's label — unconfirmed, and it has disagreed with where a box actually landed, so it is reported separately and promises nothing.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegionList"},"examples":{"default":{"value":{"default":"EU","regions":[{"code":"EU","location":"European Union","precision":"area","area":"European Union","country":null,"country_code":null,"city":null,"confirmed_on":null,"city_expected":null,"surcharge_usdc_per_month":0,"is_default":true,"note":"Spans several member states — we guarantee the EU, not a country within it. One box sold as EU (2026-08-28) geolocated to north-eastern France. If you need a specific country for data residency, ask via POST /feedback before buying."},{"code":"US-central","location":"United States (Central)","precision":"country","area":"United States (Central)","country":"United States","country_code":"US","city":null,"confirmed_on":null,"city_expected":"St. Louis","surcharge_usdc_per_month":3,"is_default":false}]}}}}}}}}},"/feedback":{"get":{"operationId":"feedbackUsage","summary":"How to send feedback. Free.","responses":{"200":{"description":"Usage instructions."}}},"post":{"operationId":"sendFeedback","summary":"Tell a human something is wrong. Free, no auth, no payment.","description":"Simplest form: POST your report as the raw body with Content-Type text/markdown or text/plain. Structured form: JSON {message, rental_id?, token?, contact?}. Include rental_id + feedback_token to be flagged as a verified customer — it is compared and never stored. Never send root_token to this endpoint; it is ignored and answered with a warning. Wanted most: anything you had to guess, anything that looked broken but wasn't, and any documentation that was wrong.","requestBody":{"required":true,"content":{"text/markdown":{"schema":{"type":"string"},"examples":{"default":{"value":"EXAMPLE — replace this line with your own report: what you did, what you expected, what happened instead."}}},"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackBody"},"examples":{"default":{"value":{"message":"EXAMPLE — replace this line with your own report: what you did, what you expected, what happened instead.","rental_id":"<optional: your rental_id>","feedback_token":"<optional: feedback_token from POST /rent or GET /instance>","contact":"<optional: an email, URL or handle we can reply to>"}}}}}},"responses":{"200":{"description":"Filed."},"400":{"description":"Empty or unparseable."},"413":{"description":"Over 8000 characters."},"429":{"description":"Hourly cap reached across all callers."}}}},"/healthz":{"get":{"operationId":"healthz","summary":"Liveness probe.","responses":{"200":{"description":"OK"}}}}},"components":{"schemas":{"CatalogList":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","enum":["sandbox_1h","sandbox_6h","sandbox_24h","server_small_1mo","server_small_12mo","server_medium_1mo","server_medium_12mo","server_large_1mo","server_large_12mo"]},"track":{"type":"string","enum":["sandbox","server"]},"price_usdc":{"type":"number"},"duration_minutes":{"type":"integer"},"spec_floor":{"type":"object","properties":{"vcpu":{"type":"integer"},"memory_mb":{"type":"integer"}}},"network":{"type":"object","description":"Uplink. `unmetered` means no traffic billing — not infinite bandwidth.","properties":{"port_mbits":{"type":"integer","description":"Port speed in Mbit/s."},"traffic":{"type":"string","const":"unmetered"}}},"disk":{"type":"object","description":"Root disk shipped with the box.","properties":{"size_gb":{"type":"integer"},"type":{"type":"string","enum":["SSD","NVMe"]}}},"available":{"type":"boolean"},"description":{"type":"string"},"region_pricing":{"type":"object","description":"Server track only. `default` is the free region; `surcharge_usd` maps each deployable region code to the USDC added over the SKU window. Add to price_usdc; pass the code as `region` in POST /rent.","properties":{"default":{"type":"string"},"surcharge_usd":{"type":"object","additionalProperties":{"type":"number"}}}}}}},"PaymentChallenge":{"type":"object","description":"The 402 body. Also base64'd into the PAYMENT-REQUIRED response header.","properties":{"x402Version":{"type":"integer","const":2},"resource":{"type":"object","description":"What you are buying. Echo it back unchanged in the payment payload.","properties":{"url":{"type":"string","format":"uri"},"description":{"type":"string"},"mimeType":{"type":"string","const":"application/json"}}},"accepts":{"type":"array","items":{"$ref":"#/components/schemas/PaymentRequirements"}},"extensions":{"type":"object","description":"Discovery metadata under `bazaar`. Echo it back unchanged."}}},"PaymentRequirements":{"type":"object","description":"One acceptable way to pay. v2 shape — note `amount`, not `maxAmountRequired`.","properties":{"scheme":{"type":"string","const":"exact"},"network":{"type":"string","const":"eip155:8453","description":"CAIP-2 chain id."},"amount":{"type":"string","description":"Exact price in USDC base units (6 decimals). Sign this value verbatim."},"asset":{"type":"string","const":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"},"payTo":{"type":"string","const":"0x8eCbd65228930060363C70854da7D37D57ffC358"},"maxTimeoutSeconds":{"type":"integer","description":"How long the signed authorization stays claimable."},"extra":{"type":"object","description":"EIP-712 domain for the asset: {name, version}."}}},"RegionList":{"type":"object","properties":{"default":{"type":"string","description":"Region billed with no surcharge when `region` is omitted."},"regions":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","description":"Pass verbatim as `region` in POST /rent."},"location":{"type":"string","description":"The most precise placement we will stand behind."},"precision":{"type":"string","enum":["city","country","area"],"description":"What `location` rests on. `city` = confirmed from inside a delivered box."},"area":{"type":"string","description":"The area we commit to. Always present."},"country":{"type":["string","null"],"description":"Null when the region spans several countries (EU does)."},"country_code":{"type":["string","null"],"description":"ISO 3166-1 alpha-2."},"city":{"type":["string","null"],"description":"Set only where a box we sold was geolocated from inside it."},"confirmed_on":{"type":["string","null"],"description":"Date of that in-box confirmation. Non-null exactly when `city` is."},"city_expected":{"type":["string","null"],"description":"Upstream's listed city. UNCONFIRMED — has disagreed with reality; never a guarantee."},"surcharge_usdc_per_month":{"type":"number","description":"Added per month of the SKU term, on top of the base price."},"is_default":{"type":"boolean"},"note":{"type":"string","description":"Caveats worth reading before buying this region."}}}},"note":{"type":"string"},"location_note":{"type":"string","description":"How to read `location` / `precision` / `city_expected`."}}},"FeedbackBody":{"type":"object","required":["message"],"properties":{"message":{"type":"string","minLength":1,"maxLength":8000},"rental_id":{"type":"string","description":"Optional. With `feedback_token`, flags the report as coming from a verified customer."},"feedback_token":{"type":"string","description":"Optional. From your POST /rent or GET /instance response. Scoped to this single purpose — it badges a report and authorizes nothing else. Do NOT send root_token here: it authorizes credential retrieval and renewal payments, this endpoint never asks for it, and it is ignored if sent."},"contact":{"type":"string","description":"Optional. Anything we can reply to. Omit to stay anonymous."}}},"RentBody":{"type":"object","required":["sku"],"properties":{"sku":{"type":"string","enum":["sandbox_1h","sandbox_6h","sandbox_24h","server_small_1mo","server_small_12mo","server_medium_1mo","server_medium_12mo","server_large_1mo","server_large_12mo"]},"region":{"type":"string","enum":["EU","US-central","US-east","US-west","SIN","UK","AUS","JPN","IND"],"description":"Optional deployment region. Defaults to EU (free). Non-EU regions carry a per-month surcharge that is included in the priced 402 challenge — see `region_pricing` in GET /catalog for the exact amounts. An unknown code is rejected with 400."}}},"RentalCreated":{"type":"object","properties":{"rental_id":{"type":"string","format":"uuid"},"sku":{"type":"string","enum":["sandbox_1h","sandbox_6h","sandbox_24h","server_small_1mo","server_small_12mo","server_medium_1mo","server_medium_12mo","server_large_1mo","server_large_12mo"]},"ipv4":{"type":["string","null"],"description":"Box IPv4. May be null at first; poll GET /instance/{id}."},"expires_at":{"type":"integer","description":"Unix ms."},"root_token":{"type":"string","description":"Bearer for GET /instance/{id} and POST /rentals/{id}/renew. Treat as a credential."},"root_user":{"type":"string","const":"root","description":"SSH username."},"root_password":{"type":"string","description":"SSH password — set via cloud-init at first boot. Re-fetchable via GET /instance/{id}?token=…"},"status":{"type":"string","enum":["pending_settlement"]},"note":{"type":"string"}}},"RentalStatus":{"type":"object","properties":{"rental_id":{"type":"string","format":"uuid"},"sku":{"type":"string","enum":["sandbox_1h","sandbox_6h","sandbox_24h","server_small_1mo","server_small_12mo","server_medium_1mo","server_medium_12mo","server_large_1mo","server_large_12mo"]},"track":{"type":"string","enum":["sandbox","server"]},"status":{"type":"string","enum":["pending_settlement","active","renewing","expired"]},"ipv4":{"type":["string","null"],"description":"Public IPv4 of the box. Null until it finishes booting (3–5 min) — poll until set. SSH credentials live on GET /instance/{rental_id}?token=…, not here."},"created_at":{"type":"integer"},"expires_at":{"type":"integer"},"grace_until":{"type":["integer","null"]},"settle_tx_hash":{"type":["string","null"]},"renewal_count":{"type":"integer"},"last_renewal_tx_hash":{"type":["string","null"]},"note":{"type":"string","description":"Where to go next, in plain language."}}},"RenewBody":{"type":"object","properties":{"periods":{"type":"integer","minimum":1,"maximum":12,"default":1}}},"RenewalCreated":{"type":"object","properties":{"rental_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["renewing"]},"periods":{"type":"integer","description":"Periods actually applied. Compare it against what you asked for before you trust the renewal."},"price_usdc":{"type":"string","description":"What the signed authorization is for, in USDC."},"current_expires_at":{"type":"integer"},"predicted_expires_at":{"type":"integer"},"manual_step":{"type":"string","description":"What you have to do. Normally nothing — the extension is applied automatically. Kept for agents written against the original response shape, when this step needed a human."}}},"InstanceStatus":{"type":"object","description":"Everything needed to reach the box. `connection` is the preferred shape; `root_user`/`root_password` are kept alongside it for agents written against the original response.","properties":{"rental_id":{"type":"string","format":"uuid"},"sku":{"type":"string","enum":["sandbox_1h","sandbox_6h","sandbox_24h","server_small_1mo","server_small_12mo","server_medium_1mo","server_medium_12mo","server_large_1mo","server_large_12mo"]},"region":{"type":"string","description":"Region code the box was deployed to."},"specs":{"type":"object","properties":{"vcpu":{"type":"integer"},"memory_mb":{"type":"integer"},"disk_gb":{"type":"integer"},"port_mbits":{"type":"integer","description":"Uplink speed in Mbit/s."},"traffic":{"type":"string","const":"unmetered","description":"No traffic billing."},"disk_type":{"type":"string","enum":["SSD","NVMe"]}}},"phase":{"type":"string","enum":["provisioning","ready","expired"],"description":"Answers 'can I SSH in yet?'. Poll until `ready` (typically 3–5 minutes after /rent)."},"status":{"type":"string","enum":["pending_settlement","active","renewing","expired"],"description":"Billing lifecycle. Independent of `phase`: a running box reads `pending_settlement` until the worker settles."},"running":{"type":"boolean","description":"True once TCP :22 answers — the same signal that triggers settlement."},"ipv4":{"type":["string","null"]},"connection":{"type":["object","null"],"description":"Null until an IPv4 is assigned.","properties":{"host":{"type":"string"},"port":{"type":"integer","const":22},"user":{"type":"string","const":"root"},"password":{"type":"string"},"command":{"type":"string","description":"Paste-ready ssh invocation."}}},"root_user":{"type":"string","const":"root"},"root_password":{"type":"string"},"expires_at":{"type":"integer","description":"Unix ms. The box is destroyed at this time."},"expires_in_seconds":{"type":"integer"},"renewal_count":{"type":"integer"},"renewal":{"type":["object","null"],"description":"Present only while a renewal is in flight. The payment is authorized and held, never captured, until the extension is actually in place — which is why `expires_at` above has not moved. `charged_usdc` is what has actually left your wallet (always 0.00 while this block exists); `charges_on_success_usdc` is what gets captured if it completes. If it never completes you are charged nothing and the rental returns to `active`.","properties":{"state":{"type":"string","enum":["awaiting_extension","finalizing"],"description":"`awaiting_extension`: the extension is not in place upstream yet. `finalizing`: it is, and capture follows within a minute or two."},"periods":{"type":"integer"},"charged_usdc":{"type":"string","description":"What has actually left your wallet. 0.00 for as long as this block is visible."},"charges_on_success_usdc":{"type":"string","description":"What is captured if, and only if, the extension completes."},"current_expires_at":{"type":"integer"},"predicted_expires_at":{"type":"integer"},"abandoned_after":{"type":"integer","description":"Unix ms. If the extension isn't in place by then the renewal is dropped, nothing is charged, and the rental returns to `active` so you can retry."},"note":{"type":"string"}}},"renew_url":{"type":"string","description":"Pre-signed renewal URL for this rental (token included). Carries no `periods` — append &periods=N for N terms."},"renew_periods_note":{"type":"string","description":"How to ask for more than one period on renew_url."},"note":{"type":"string","description":"Human-readable guidance for the current phase."}}}}}}