# vm402 docs

## Buy a VM (the only required call)

`POST /vms` buys the machine — everything after it is optional. It's a
paid endpoint: calling it with no payment returns a **402** whose body
carries a lightning invoice (L402), USDC-on-Base requirements (x402), and
a `how_to_pay` walkthrough:

    curl -X POST https://vm402.com/vms -H "Content-Type: application/json" \
      -d '{"hours": 24}'

Pay one rail (an agentic wallet settles this automatically — raw curl
can't complete the payment), then repeat the request with the payment
credential attached. The paid retry returns the VM: `id`, `token`
(the bearer token for every later call — shown once, keep it), `url`,
`balance_seconds` (your credit balance), and `meter`. Exact per-rail
retry headers: [/api-docs](https://vm402.com/api-docs).

Every example below is a complete, runnable command — set these two
values from the create response first:

    TOKEN="<token from POST /vms>"
    ID="<id from POST /vms>"

## Get your code onto a VM

The reliable way is a tarball: build it locally, upload the bytes, extract.
Works for any file (including secrets like a `.env` — the bytes stream
through, nothing is logged).

    # local: bundle your app (skip node_modules etc — install on the VM)
    tar czf bundle.tar.gz --exclude=node_modules --exclude=.git .

    # upload (raw body, binary-safe, no size cap)
    curl -X PUT "https://vm402.com/vms/$ID/files?path=/root/bundle.tar.gz" \
      -H "Authorization: Bearer $TOKEN" --data-binary @bundle.tar.gz

    # extract into /app + install on the VM
    curl -X POST https://vm402.com/vms/$ID/exec -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"command": "mkdir -p /app && tar xzf /root/bundle.tar.gz -C /app && cd /app && npm install"}'

## Just run code (nothing exposed)

    curl -X POST https://vm402.com/vms/$ID/exec -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"command": "python3 -c \"print(6*7)\""}'

The filesystem persists between commands and across sleep. Install
anything (apt, npm, pip). Come back tomorrow; your environment is intact
while the meter runs.

## Host a web app

1. Get your files onto the VM — upload a tarball (above) or `git clone`
   via exec, then extract into /app and install deps:

       curl -X POST https://vm402.com/vms/$ID/exec -H "Authorization: Bearer $TOKEN" \
         -H "Content-Type: application/json" \
         -d '{"command": "mkdir -p /app && tar xzf /root/bundle.tar.gz -C /app && cd /app && npm install"}'

2. Register your server as a **managed service** so it survives sleep/wake.
   A plain background process (`node server.js &`) dies as soon as the
   exec command returns — a managed service is the only thing that keeps
   serving:

       curl -X PUT https://vm402.com/vms/$ID/services/web -H "Authorization: Bearer $TOKEN" \
         -H "Content-Type: application/json" \
         -d '{"cmd": "node", "args": ["server.js"], "dir": "/app", "env": {"PORT": "8080"}, "http_port": 8080}'

   The response includes the service status and any startup output, so a
   crash-on-boot is visible immediately. Manage it later:

       # list services + status
       curl https://vm402.com/vms/$ID/services -H "Authorization: Bearer $TOKEN"

       # recent logs
       curl "https://vm402.com/vms/$ID/services/web/logs?lines=100" -H "Authorization: Bearer $TOKEN"

       # restart after deploying new code (also: /start, /stop)
       curl -X POST https://vm402.com/vms/$ID/services/web/restart -H "Authorization: Bearer $TOKEN"

       # remove it
       curl -X DELETE https://vm402.com/vms/$ID/services/web -H "Authorization: Bearer $TOKEN"

3. Make the URL public:

       curl -X PATCH https://vm402.com/vms/$ID/url -H "Authorization: Bearer $TOKEN" \
         -H "Content-Type: application/json" -d '{"visibility": "public"}'

Your app is at `https://$ID.vm402.com`. It sleeps when nobody
visits (billed at the asleep rate, 0.05 credit-hours
per hour), wakes in under a second on the next request, and the service
restarts automatically — the URL is always online even though the VM
sleeps.

## Buy a subdomain alias ($0.20)

The default URL carries the VM's random 8-hex id. For a flat
**$0.20 (or 200 sats)** you can attach a name
of your choice:

    curl -X POST https://vm402.com/vms/$ID/aliases -H "Content-Type: application/json" \
      -d '{"name": "myapp"}'

It's a paid endpoint with the same 402 flow as create/topup: the first call
returns the challenges, pay one rail, repeat the request (same
`{"name": ...}` body) with the payment credential. The paid retry returns
`https://myapp.vm402.com`, which serves exactly what the VM URL
serves — same app, same public/private visibility, same wake-on-request.

- **First come, first served**, globally unique. Buy as many per VM as you
  want (each priced separately). No VM token needed to buy.
- **An alias lives exactly as long as its VM.** When the VM is deleted — by
  you, or reclaimed after its credit ran out and grace expired — every alias
  on it is deleted too and the name frees up for anyone. No refunds.
- Names: one DNS label — lowercase letters, digits, hyphens, 1–63 chars.
  Reserved names and anything shaped like a VM id (8 hex chars) are rejected.
- Manage with the VM token: `GET /vms/$ID/aliases` lists them,
  `DELETE /vms/$ID/aliases/myapp` removes one (frees the name, no refund).
- No abuse: an alias that impersonates a person, brand, or service (or is
  otherwise abusive) will be deleted — we may remove any alias at any time,
  without notice or refund ([terms](https://vm402.com/terms)).

## Gotchas

- A process started via exec **dies the moment the command returns** —
  `nohup app &` is dead before you can visit it, and holding it in the
  foreground just hits the exec timeout (120s default). Anything that
  should keep running must be a managed service:
  `PUT /vms/{id}/services/{name}` (see "Host a web app").
- The public URL routes to the one service holding `http_port` (any
  port — requests auto-start it). With no `http_port` registered, the
  URL falls back to raw port 8080.
- The VM sleeps ~30s after activity stops. Suspended VMs can't receive
  outbound events (webhooks/subscriptions are frozen until the next
  inbound request wakes them). Design event-driven apps to reconcile on
  wake.
- Exec sessions and open connections keep the VM awake; close them.

## How billing & sleep work

You buy **credit-hours** and they drain at two rates:

- **Awake: 1 credit-hour per hour.** The VM is awake while it's handling
  an HTTP request, running an exec'd command, or holding network activity —
  a process that keeps talking to the network (polling loops, subscriptions,
  open connections) keeps the VM awake continuously and is billed
  continuously.
- **Asleep: 0.05 credit-hours per hour.** A flat fee that
  keeps your disk — files, installed packages, and registered services
  all persist. It's the same fee regardless of how much you store.

Awake time is metered in ~10-minute intervals: an interval counts as awake
if the VM did any work in it — handled a request, ran a command, or was
observed running. A 2-minute command bills the interval it ran in; an
interval with no activity at all bills at the asleep rate.

What that means in practice:

- **Sleep is automatic and applies to every VM** — no flag, no config, no
  app-type restriction. Roughly 30 seconds after the last activity the VM
  suspends. Nothing opts out of sleep except actually doing work.
- **Wake is automatic and effectively invisible**: any request to the VM's
  URL, or any exec/file/API call, wakes it in well under a second.
  Visitors don't notice; you don't manage it.
- **Registered services do NOT keep the VM awake** by merely existing —
  between requests they sleep with the VM and are resumed/restarted on
  wake. That's why they're the hosting primitive. What DOES keep it awake:
  in-flight requests, running commands, open exec/TTY sessions, and open
  TCP connections (inbound or outbound). A pure CPU loop with no network
  activity is simply frozen mid-computation when the VM suspends and
  resumes on the next wake — it does not hold the VM awake.
- Worked example: a web app serving ~100 requests/day is awake maybe
  0.1 h/day → ~0.1 + ~1.2 ≈
  1.3 credit-hours/day. A loop that
  polls the network burns 24/day. Watch `total_awake_seconds` on the
  meter — if it tracks wall-clock, your app never sleeps and you should
  fix that.

**When the balance hits 0**, all compute stops: requests and API calls
return 402 immediately, running processes are terminated, and the VM is
shut down. Your disk is kept through a grace period you've earned —
**1 day + 1 day per 24 credit-hours ever purchased**
for this VM. The disk keeps billing at the asleep rate through grace, so
the balance goes negative; a topup must cover the debt first
(`GET /vms/{id}/meter` shows the exact numbers and `grace_until`).
After grace, the VM and its disk are **deleted permanently**. If your
services were stopped at exhaustion, a reviving topup restarts them.

**Watching the balance is your job.** There are no accounts, so there is
nobody to email and no warning before the meter runs out — the meter *is*
the warning. If you (or your agent) care about a VM, check
`GET /vms/{id}/meter` on a schedule and top up before `balance_seconds`
reaches 0 — and treat `grace_until` as a hard deadline: past it the VM
and its disk are unrecoverable. The meter and topup are public (no VM
token needed), so a watchdog agent, a cron job, or a generous stranger
can keep any VM alive.

## Meter & payment

- Price: **10 sats per credit-hour** over L402
  (lightning), or **$0.01 per credit-hour** in USDC over
  x402 (Base). Create and topup both take an optional `{"hours": n}` body
  (create defaults to 24h) and cost `hours ×` the
  rate.
- Both `POST /vms` and `POST /vms/{id}/topup` are paid endpoints. Call with
  no payment → 402 carrying both challenges. L402: pay the invoice, retry with
  `Authorization: L402 <token>:<preimage>`. x402: sign the `exact`-scheme
  USDC payment for the `accepts` entry (in the body and the
  `payment-required` header), retry with it base64-encoded in the
  `payment-signature` header.
- Other rails (x402 on Solana, MPP on Tempo) still work via the l402.space
  proxy — the 402 body links them under `pay_with_other_rails`.
- `GET /vms/{id}/meter` is public — status pages and other agents can poll
  it. Topup is public too (no VM token needed): anyone can feed any meter.
- Subdomain aliases are the third paid endpoint:
  `POST /vms/{id}/aliases {"name": "myapp"}`, flat
  **200 sats / $0.20** each (see "Buy a
  subdomain alias" above).
