Skip to main content

Instances

An instance is one rented offer running one template. These endpoints do everything the Instances page does.

Deploy an instance​

curl -s -X POST "$SUPERHEAT_API/v1/instances" \
-H "Authorization: Bearer $SUPERHEAT_KEY" \
-H "Content-Type: application/json" \
-d '{
"offer_id": "5d2f9c31-8b64-4c0e-9a77-2e0f1b6a4c11",
"template_id": "b1f4a6d2-90c7-5e33-8a2b-1d7c4e0f9a56",
"disk_gb": 60,
"label": "sft-run-14",
"ssh_key_id": "7a3c1e08-42bd-4f19-9d63-5b8e0c2a1f40",
"env_overrides": [
{"key": "HF_TOKEN", "value": "hf_...", "secret": true}
]
}'
FieldTypeRequiredNotes
offer_iduuidYesMust still be available, or the call returns 409 OFFER_UNAVAILABLE
template_iduuidYesA system template, a public one, or one your organization owns
disk_gbintegerYesBetween 10 and 20000, and no larger than the host's machine.disk_gb
labelstring, up to 64 charactersNoShows in the console list. Defaults to null.
ssh_key_iduuidNoWith a signed-in session or a personal API key: one of your own SSH keys. With a team API key: the key of anyone who is a member of the organization now — never a former member's, the key's own creator included once they have left. Without it, the instance has no authorized key and you cannot SSH in.
env_overridesarray of {key, value, secret}NoMerged over the template's env, matched by key. An entry with a null value is dropped. At most 100 entries; keys up to 256 characters, values up to 4,096.

Environment overrides are merged over the template's env, but the platform's own overlay wins over both. You cannot shadow JUPYTER_TOKEN, GPU_COUNT or the instance access token (SUPERHEAT_ACCESS_TOKEN, and any template variable that carries it).

The response is 201 with the instance. Its ssh_host and ssh_port are still null at this point; they are filled in once the workload is up. Billing starts when the workload actually reaches running, not at the moment of this call.

A deploy spends money the moment it runs

The balance has to cover one hour of the organization's whole burn when you deploy an instance, and again when you start one: the new instance's GPU and disk, plus the GPU and disk of every instance that is running, starting or being created and the disk of every one that is stopped. Nothing is reserved or held back: GPU time is billed per second while the instance is running, and disk per GB per hour while it is stopped. At a zero balance running instances are stopped automatically; after 24 hours at or below zero, once the balance is at or below −$5, stopped and errored instances are destroyed. See How billing works.

When a deploy is refused​

A refused deploy returns one of these codes. The body carries the extra fields named here next to code and message.

StatusCodeWhen
402INSUFFICIENT_BALANCEThe balance does not cover one hour of the organization's burn with this instance added. Carries balance_micro and required_micro
402PAYMENTS_FROZENA payment dispute on the organization is still open
409OFFER_UNAVAILABLEThe offer is unknown, withdrawn, or has no free unit left — including a unit another deploy took while yours was being reserved
409MACHINE_IN_MAINTENANCEThe machine is booked to go offline too soon to start on. Carries next_maintenance
409MACHINE_DISK_EXHAUSTEDThe machine's disk is already committed to other renters. Carries available_disk_gb and requested_disk_gb
409INSTANCE_LIMIT_REACHEDThe organization already has 25 instances that are not destroyed. Stopped and errored instances count. Carries limit
409OFFER_NOT_PINNABLEThe machine is not registered with the control plane, so the deploy cannot be pinned to it
422DISK_TOO_LARGEdisk_gb is more than the machine has in total. Carries max_disk_gb
422OFFER_NOT_VM_CAPABLEA vm template on a machine that cannot run virtual machines
422TEMPLATE_COMING_SOONThe template's image has not been published yet
422TEMPLATE_CUDA_TOO_NEWThe template supports CUDA only up to its cuda_max, and the machine reports a newer version
422TEMPLATE_ARCH_UNSUPPORTEDThe template needs hardware other than x86_64. Every Superheat host is x86_64
422TEMPLATE_REQUIRES_OWNERThe template pulls from a private registry, and only the organization that owns it can deploy it
404TEMPLATE_NOT_FOUNDThe template does not exist, is not visible to your organization, or has been deactivated
404SSH_KEY_NOT_FOUNDssh_key_id is not a key this credential may attach (see ssh_key_id above), or does not exist

Every code in this table is returned before anything is kept: a refused deploy leaves no instance behind and costs nothing. Offers on unregistered machines are left out of GET /v1/offers, so you normally meet OFFER_NOT_PINNABLE only with an offer id saved from earlier.

What to do about each code is in Error codes.

Instance fields​

{
"id": "e08b5f27-1c4a-4d90-b3e6-72a9d1f45c83",
"label": "sft-run-14",
"status": "creating",
"disk_gb": 60,
"price_per_hour_micro": 2290000,
"storage_price_per_gb_hour_micro": 250,
"ssh_host": null,
"ssh_port": null,
"ssh_user": "root",
"jupyter_url": null,
"open_url": null,
"access_token": null,
"error_code": null,
"error_message": null,
"allowed_actions": ["destroy"],
"gpus_released": false,
"gpu_release": null,
"gpu_release_requested_at": null,
"stop_releases_gpus": false,
"hold_price_per_hour_micro": null,
"stop_hold_price_per_hour_micro": null,
"suspended_hold_price_per_hour_micro": null,
"created_at": "2026-07-24T11:12:03.771Z",
"started_at": null,
"status_changed_at": "2026-07-24T11:12:03.771Z",
"destroyed_at": null,
"spend_micro": 0,
"deployed_by": "you@example.com",
"deployed_via_api_key": null,
"offer": { "id": "5d2f9c31-8b64-4c0e-9a77-2e0f1b6a4c11" },
"template": { "id": "b1f4a6d2-90c7-5e33-8a2b-1d7c4e0f9a56" }
}

offer and template are the objects documented in Offers and Templates; they are shown abbreviated here. The embedded template carries readme and onstart as null — fetch GET /v1/templates/{id} when you need them.

FieldTypeMeaning
statusstringOne of creating, starting, running, stopping, stopped, destroying, destroyed, error. See Instance states.
disk_gbintegerDisk you asked for, billed per GB per hour while stopped
price_per_hour_microintegerRate snapshotted from the offer at rent time, for the whole slice
storage_price_per_gb_hour_microintegerDisk rate snapshotted at rent time
ssh_host, ssh_port, ssh_userstring or null, integer or null, stringThe connect target. ssh_host is the GPU machine's public IP address. The port is assigned from the host's published range and is never 22. Host and port are null until the workload is up.
jupyter_urlstring or nullJupyterLab entry point, once a jupyter template reports one. Carries the access token as ?token=
open_urlstring or nullThe template's open-button endpoint, once it is reported. Carries ?token= when the open-button port is guarded by the access proxy
access_tokenstring or nullThe instance's access token: the JupyterLab token, the bearer key its template's app ports expect, and the password of any account the template pre-creates. null for a template that uses none, and once the instance is destroyed. See Ports and the Open button
error_code, error_messagestring or nullWhy the instance is in error — for example provision_failed or workload_completed — or, on a running or stopped instance, why a stop or start did not land (stop_timed_out, start_timed_out, gpus_unavailable). null otherwise. The instance page in the console explains the code in your console language; every code it knows is listed in Error codes
allowed_actionsarrayWhich of start, stop and destroy the API accepts in the current state
gpus_releasedbooleantrue once the stopped instance's GPUs have been released back to the machine — the balance reached zero, or a payment dispute froze the organization's payments. Its container and disk are still there, and a start has to re-claim the same GPUs: it answers 409 CAPACITY_UNAVAILABLE while another renter uses one of them. See Stop vs destroy
gpu_releasestring or nullWhere a release stands: pending (decided, not yet confirmed by the machine), released, or unsupported (the machine cannot release GPUs, or the instance runs a vm template, so they stay reserved and the instance bills storage only, as after your own stop). null when no release was decided
gpu_release_requested_attimestamp or nullWhen the release was decided
stop_releases_gpusbooleantrue while a payment dispute has frozen the organization's payments: a stop requested now releases the GPUs, where the machine supports it, instead of keeping them reserved
hold_price_per_hour_micro, stop_hold_price_per_hour_micro, suspended_hold_price_per_hour_microinteger or nullReserved for a GPU hold fee on stopped instances. No such fee is charged — a stopped instance bills storage only — so all three are null
created_at, started_at, status_changed_at, destroyed_attimestampsstarted_at is set the first time the workload runs
spend_microintegerSettled charges for this instance so far. It moves with every settlement, in the same transaction as the ledger row, so it always equals the sum of the instance's charges on the transactions list
deployed_bystringEmail of the member who deployed it. For an instance deployed with a team API key, the key instead: its name and prefix, such as CI deploys (shk_1a2b3c4d…)
deployed_via_api_keyobject or nullThe API key the instance was deployed with — id, name, kind (team or personal), prefix and revoked — or null for a console deploy and for instances deployed before this was recorded

List instances​

curl -s "$SUPERHEAT_API/v1/instances" \
-H "Authorization: Bearer $SUPERHEAT_KEY"

Returns {"items": [...]} for the active organization, newest first. Destroyed instances are excluded unless you pass include_destroyed=true.

Get one instance​

curl -s "$SUPERHEAT_API/v1/instances/e08b5f27-1c4a-4d90-b3e6-72a9d1f45c83" \
-H "Authorization: Bearer $SUPERHEAT_KEY"

Unknown ids, and instances belonging to another organization, return 404 INSTANCE_NOT_FOUND.

Stop, start and destroy​

RequestEffectAllowed from
POST /v1/instances/{id}/stopShuts the container down, keeps the disk and holds the GPU slot — or, while a payment dispute has frozen the organization's payments, releases the GPUs where the machine supports it (stop_releases_gpus). Status goes to stopping, then stopped.running
POST /v1/instances/{id}/startBrings a stopped instance back on the same disk and the same GPUs, which an instance with gpus_released has to re-claim. Status goes to starting, then running.stopped
DELETE /v1/instances/{id}Tears the instance down and releases the slice. Status goes to destroying, then destroyed. On an instance already destroying it re-sends the teardown and returns 200.every state except destroyed
curl -s -X POST "$SUPERHEAT_API/v1/instances/$INSTANCE_ID/stop" \
-H "Authorization: Bearer $SUPERHEAT_KEY"

All three return the full instance object at its new transitional status — the transition finishes asynchronously, so poll until the status settles.

Calling one of these from a status that does not allow it returns 409 INVALID_STATE with a message naming the current status, for example Cannot start an instance while it is running. allowed_actions on the instance lists what is legal right now. Starting re-checks the balance, which has to cover one hour of the organization's burn with this instance running — and can return 402 INSUFFICIENT_BALANCE. It returns 402 PAYMENTS_FROZEN while a payment dispute is open.

A start also fails when the host cannot take it. In each case the instance is put back in stopped, with its disk — and its slot, unless its GPUs were released:

StatusCodeWhen
409CAPACITY_UNAVAILABLEThe instance's GPUs were released, and another renter is using one of them now. Try again later, or copy your data off and destroy it
409HOST_UNREACHABLEThe machine the instance lives on is not connected. Try again later
409START_REFUSEDThe host refused the resume, or no longer holds the workload — the message says which. In the second case destroy the instance and deploy again
503START_UNAVAILABLEThe resume could not be completed right now. Try again shortly

Stop and destroy are recorded first and sent to the host afterwards, and the platform keeps re-sending them until the host has done it. A stop the host has not confirmed after 30 minutes puts the instance back in running, billing the GPU, and the stop keeps being retried; error_code reads stop_timed_out.

Every re-send of one stop, start or destroy carries the same identity, and each new request you make gets a new one. So a stop that is re-sent after you have already started the instance again is recognised as out of date and does not stop it a second time, and a start the platform retries after it has already landed is no longer answered as a refusal. Your own calls need nothing extra: send a DELETE again if you want the teardown re-sent, and it is treated as a new request.

Destroying deletes the disk

DELETE is final: the container, the disk and everything on it are gone, and the offer goes back on the market. Stop instead if you want your data waiting for you. See Stop vs destroy.

Logs​

curl -s "$SUPERHEAT_API/v1/instances/$INSTANCE_ID/logs?tail=50" \
-H "Authorization: Bearer $SUPERHEAT_KEY"
{
"lines": [
"[2026-07-24 11:12:07] superheat-agent: pulling image vastai/pytorch",
"[2026-07-24 11:12:11] superheat-agent: image ready, creating container",
"[2026-07-24 11:12:13] nvidia-smi: detected 1 GPU(s), driver 570.86",
"[2026-07-24 11:12:14] sshd: listening on 0.0.0.0:41207",
"[2026-07-24 11:12:15] superheat-agent: instance is ready"
]
}
ParameterRangeDefault
tail1 to 1000200

Values outside that range are rejected with 422. lines is empty until the workload has run for the first time, so a creating instance returns nothing. There is no streaming endpoint; poll this one — the console refreshes it every three seconds. See Logs.

Deploy and connect, end to end​

This is the whole path from an empty shell to an SSH session. It assumes jq, an SSH key already added in the console, and credits on the balance.

1. Set your credentials. SSH keys belong to your account rather than to the organization, so listing them needs a session token; everything else runs on the API key.

export SUPERHEAT_API="https://<your-superheat-api-host>"
export SUPERHEAT_KEY="shk_your_key_here"
AUTH=(-H "Authorization: Bearer $SUPERHEAT_KEY")

2. Find the SSH key id with a session token from a signed-in browser:

curl -s "$SUPERHEAT_API/v1/ssh-keys" \
-H "Authorization: Bearer $SESSION_TOKEN" \
| jq -r '.items[] | "\(.id) \(.name) \(.fingerprint)"'
7a3c1e08-42bd-4f19-9d63-5b8e0c2a1f40 laptop SHA256:Yx1r0oW2fS7Tq8kJ3mN4pB6vC9dE0gH2iK5lM8nP1qR
SSH_KEY_ID="7a3c1e08-42bd-4f19-9d63-5b8e0c2a1f40"

3. Pick the cheapest single H100 slice:

OFFER_ID=$(curl -s "$SUPERHEAT_API/v1/offers?gpu_model=H100%20SXM&num_gpus=1&sort=price_asc" \
"${AUTH[@]}" | jq -r '.items[0].id')

4. Pick an SSH template:

TEMPLATE_ID=$(curl -s "$SUPERHEAT_API/v1/templates?tab=recommended&mode=ssh" \
"${AUTH[@]}" | jq -r '.items[0].id')

5. Deploy:

INSTANCE_ID=$(curl -s -X POST "$SUPERHEAT_API/v1/instances" \
"${AUTH[@]}" -H "Content-Type: application/json" \
-d "{\"offer_id\":\"$OFFER_ID\",\"template_id\":\"$TEMPLATE_ID\",\"disk_gb\":60,\"label\":\"api-demo\",\"ssh_key_id\":\"$SSH_KEY_ID\"}" \
| jq -r '.id')

6. Wait for running:

while :; do
STATUS=$(curl -s "$SUPERHEAT_API/v1/instances/$INSTANCE_ID" "${AUTH[@]}" | jq -r '.status')
echo "$STATUS"
case "$STATUS" in
running) break ;;
error|destroyed) exit 1 ;;
esac
sleep 3
done

7. Build the connect command from the instance itself rather than reconstructing it — the port is assigned per instance:

curl -s "$SUPERHEAT_API/v1/instances/$INSTANCE_ID" "${AUTH[@]}" \
| jq -r '"ssh -p \(.ssh_port) \(.ssh_user)@\(.ssh_host)"'
ssh -p 41207 root@203.0.113.14

8. Connect and check the GPUs:

ssh -p 41207 root@203.0.113.14 nvidia-smi

9. Stop when you pause, destroy when you are done:

curl -s -X POST "$SUPERHEAT_API/v1/instances/$INSTANCE_ID/stop" "${AUTH[@]}" | jq -r '.status'
curl -s -X DELETE "$SUPERHEAT_API/v1/instances/$INSTANCE_ID" "${AUTH[@]}" | jq -r '.status'