Public API / version 2Control a VPS with ordinary HTTP.
Discover live images, prepare and pay for a 30-day VPS, poll it to ready, and manage it with the one-time owner token. WarpMetal keeps the underlying compute provider and its credentials out of the public contract.
- Base URL
https://api.warpmetal.com- Content type
application/json
Credential boundaryA wallet authorizes a specific x402 payment. It does not control the server. The ownerToken returned by order preparation controls status, renewal, and lifecycle operations. It is shown once; store it like a password and never put it in a URL, log, or client-side analytics event.
01Headers and authentication
There is no account login and no reusable provider API key.
AuthorizationBearer <ownerToken>Required for checkout, task reads, renewal, and lifecycle operations.
Idempotency-Key8–160 charactersRequired for order creation and every mutating lifecycle request. Use a new value for each new action.
PAYMENT-SIGNATUREx402 v2 payloadAdded only after signing a live PAYMENT-REQUIRED challenge.
02Plans and available operating systems
Never hard-code image names. Inventory can change.
GET/api/catalog
No authentication. Returns products[]. Choose a product by id, then choose an OS from that same product's operatingSystems[]. Submit the exactname value as osName when ordering or reloading. WarpMetal only publishes images withmonthlyPrice: 0 and sshKey: true.
curl --fail https://api.warpmetal.com/api/catalog | jq '
.products[] |
{planId: .id, priceUsd, termDays,
operatingSystems: [.operatingSystems[] |
{name, label, sshKey, cloudInit, monthlyPrice}]}'
# Select one exact OS name for the standard plan
OS_NAME=$(curl -fsS https://api.warpmetal.com/api/catalog |
jq -r '.products[] | select(.id == "standard") |
.operatingSystems[] |
select(.monthlyPrice == 0 and .sshKey == true) |
.name' | head -n 1)
HTTP 503 means fresh catalog inventory is unavailable. Pause ordering and reload requests instead of guessing an OS name.
03Prepare an order
Preparation does not charge a wallet or provision a server.
POST/api/orders
Required body fields are planId,hostname, osName, and an OpenSSHsshPublicKey. email is optional and only enables notices; it is not an account or recovery method.
curl -i -X POST https://api.warpmetal.com/api/orders \
-H 'content-type: application/json' \
-H 'idempotency-key: order-<unique-value>' \
--data '{
"planId": "standard",
"hostname": "agent-workspace",
"osName": "<exact products[].operatingSystems[].name>",
"sshPublicKey": "ssh-ed25519 <public-key-data> agent@example"
}'
HTTP 201 returns task and the one-timeownerToken. An exact idempotent replay returns the same response for 24 hours; the same key with different JSON returns HTTP 409.
04Pay the x402 challenge
The live 402 is authoritative for price, asset, network, and recipient.
POST/api/checkout/{planId}
curl -i -X POST https://api.warpmetal.com/api/checkout/standard \
-H 'content-type: application/json' \
-H 'authorization: Bearer <ownerToken>' \
--data '{"taskId":"task_..."}'
- HTTP 402: sign the
PAYMENT-REQUIRED header and retry the exact method, URL, content type, and body with PAYMENT-SIGNATURE. - HTTP 202 with
payment_pending: wait for Retry-After, then retry the exact request with the exact same signature. - A signed HTTP 402 definitively rejected that signature. Read the new challenge and create one replacement authorization.
- HTTP 409
manual_review: payment may be final. Never pay again. - HTTP 202
provisioning: payment is final; poll the task.
05Poll the task and connect with SSH
The task is the durable view of purchase and provisioning state.
GET/api/tasks/{taskId}
curl -fsS https://api.warpmetal.com/api/tasks/task_... \
-H 'authorization: Bearer <ownerToken>'
Follow pollAfterSeconds. Continue while state isprepared, payment_pending,paid, or provisioning. When it isready, use publicIp with the private key matching the submitted public key. SSH usernames are image-specific; this API does not publish or guess one.
Terminal states are expired,cancellation_pending, cancelled, andfailed. For manual_review, stop automation and do not pay or mutate the resource again.
06Lifecycle operation model
Power, reload, and network mutations are durable asynchronous operations.
GET/api/operations/{operationId}
A mutating lifecycle request returns HTTP 202, aLocation header, and an operation in statequeued. Poll with the same owner token until state is succeeded, failed, ormanual_review. Do not infer success from HTTP 202.
curl -fsS https://api.warpmetal.com/api/operations/op_... \
-H 'authorization: Bearer <ownerToken>'
Replaying the same action with the same Idempotency-Key returns the same operation. Reusing that key for different content returns HTTP 409.
07Power control
Available only while the task is ready.
POST/api/tasks/{taskId}/power
curl -i -X POST https://api.warpmetal.com/api/tasks/task_.../power \
-H 'content-type: application/json' \
-H 'authorization: Bearer <ownerToken>' \
-H 'idempotency-key: power-<unique-value>' \
--data '{"action":"reboot"}'
Allowed actions: boot, reboot, and shutdown.
08Destructive OS reload
Reload erases the server. It is not a reboot.
POST/api/tasks/{taskId}/reload
The task must be ready. Sendconfirm: "ERASE". The optionalhostname, osName, andsshPublicKey fields replace those values; omitted fields keep their current value. A replacement OS must be an exact live free OS name from the task plan inGET /api/catalog.
curl -i -X POST https://api.warpmetal.com/api/tasks/task_.../reload \
-H 'content-type: application/json' \
-H 'authorization: Bearer <ownerToken>' \
-H 'idempotency-key: reload-<unique-value>' \
--data '{
"confirm": "ERASE",
"hostname": "fresh-workspace",
"osName": "<exact live OS name>",
"sshPublicKey": "ssh-ed25519 <new-public-key-data> agent@example"
}'
Poll the returned operation. On succeeded, poll the task again to read the updated hostname, OS, and SSH fingerprint. If the operation becomesmanual_review, the provider outcome is ambiguous: do not submit another reload.
09Private networks
Networks are scoped to the owner token and US capacity.
GET/api/networks?taskId={taskId}
curl -fsS 'https://api.warpmetal.com/api/networks?taskId=task_...' \
-H 'authorization: Bearer <ownerToken>'
POST/api/networks
curl -i -X POST https://api.warpmetal.com/api/networks \
-H 'content-type: application/json' \
-H 'authorization: Bearer <ownerToken>' \
-H 'idempotency-key: network-create-<unique-value>' \
--data '{"taskId":"task_...","name":"agent-lan"}'
HTTP 409 means owner or platform network capacity is reached.
POST/api/tasks/{taskId}/network
curl -i -X POST https://api.warpmetal.com/api/tasks/task_.../network \
-H 'content-type: application/json' \
-H 'authorization: Bearer <ownerToken>' \
-H 'idempotency-key: network-attach-<unique-value>' \
--data '{"networkId":"net_...","action":"attach"}'
Allowed actions: attach and detach. Poll the returned operation.
10Renew for another 30 days
Renewal is explicit and requires a new x402 payment.
POST/api/checkout/{planId}/renew
curl -i -X POST https://api.warpmetal.com/api/checkout/standard/renew \
-H 'content-type: application/json' \
-H 'authorization: Bearer <ownerToken>' \
--data '{"taskId":"task_..."}'
The task must be ready or expired and still have its instance. Handle 402 and payment-pending retries exactly like initial checkout. HTTP 200 meanstermEndsAt was extended by 30 days. There is no public destroy/cancel endpoint; an unrenewed instance is cancelled by maintenance after its term ends.
11Errors, retries, and limits
All API errors use a stable JSON envelope.
{
"error": {
"code": "stable_machine_code",
"message": "Human-readable explanation."
}
}
400Invalid JSON, hostname, OS, public key, action, or missing idempotency key. Fix the request.
402Payment required, or a supplied signature was definitively rejected. Follow the returned challenge.
404The resource and owner-token pair was not found. Do not loop.
409State/idempotency conflict or manual review. Read the code; never pay again for manual review.
429Rate limited. Wait for the Retry-After header.
503A dependency or payment outcome is unavailable. For a signed payment attempt, preserve and retry the exact request.