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. 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 is checked at deploy and at start — it has to cover one hour of the organization's whole burn: this 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 stopped one — but nothing is reserved. GPU time is billed per second while the instance is running, and disk per GB per hour while it is stopped. At ¥0 running instances are stopped automatically; after 24 hours at or below ¥0, once the balance is at or below −¥35, stopped and errored instances are destroyed. See How billing works.

When a deploy is refused​

The checks run in the order below, and the first one that fails is returned. A refused deploy leaves nothing behind and costs nothing:

StatusCodeCause
409OFFER_UNAVAILABLEThe offer does not exist, has been withdrawn, or that shape is sold out on its machine
409MACHINE_IN_MAINTENANCEThe machine is booked offline too soon to start on. Carries next_maintenance
422DISK_TOO_LARGEdisk_gb is more than the machine's total disk. Carries max_disk_gb
409MACHINE_DISK_EXHAUSTEDNot enough of the machine's disk is left to commit. Carries available_disk_gb and requested_disk_gb
404TEMPLATE_NOT_FOUNDThe template does not exist, is not visible to you, or is inactive
422TEMPLATE_COMING_SOONThe template is marked coming soon; its image is not published yet
422OFFER_NOT_VM_CAPABLEA vm template on a machine that cannot run VMs
422TEMPLATE_CUDA_TOO_NEWThe template pins an older CUDA stack (cuda_max) and the machine's CUDA is newer
422TEMPLATE_ARCH_UNSUPPORTEDThe template declares an architecture other than x86_64; Superheat hosts are x86_64
422TEMPLATE_REQUIRES_OWNERAnother organization's template that pulls from a private registry with credentials; only the owning organization can deploy it
409OFFER_NOT_PINNABLEThe machine is not registered with the fleet's control plane, so the deploy cannot be pinned to it. Listings never show such an offer; this comes from a stale offer_id
404SSH_KEY_NOT_FOUNDssh_key_id is not a key this credential may attach (see ssh_key_id above), or does not exist
402INSUFFICIENT_BALANCEThe balance does not cover one hour of the organization's burn with this instance added: the offer's GPU rate plus storage for disk_gb, plus what the organization's other instances cost an hour. Carries balance_micro and required_micro
409INSTANCE_LIMIT_REACHEDThe organization already has 25 instances that are not destroyed — stopped and error ones count. Carries limit
409OFFER_UNAVAILABLEAnother deploy took the last free unit in the moment yours was being reserved. Nothing is kept

What to do about each code is in Errors.

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": "203.0.113.14",
"ssh_port": 41207,
"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,
"hold_price_per_hour_micro": null,
"stop_hold_price_per_hour_micro": null,
"stop_releases_gpus": false,
"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": {
"id": "0f5a1c93-6d21-4e7c-a0b8-3c9f2e1d4a77",
"name": "ci-pipeline",
"kind": "personal",
"prefix": "shk_9f3a21c4",
"revoked": false
},
"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, integer, stringThe connect target. ssh_host is the host's public IP address; SSH always connects directly and is never relayed. The port is assigned from the host's published range and is never 22.
jupyter_urlstring or nullJupyterLab entry point (token included), once a jupyter template reports one. When the host cannot be reached directly from outside, it is a relay address under relay.superheat.cloud — only once Superheat CN moves to its superheat.cloud domain. See Which address the button opens.
open_urlstring or nullThe template's open-button endpoint, once it is reported. It can be a relay address on the same terms. Carries ?token= when the open-button port is guarded by the access proxy
access_tokenstring or nullThe instance 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 The instance access token
error_code, error_messagestring or nullWhy the instance is in error — for example provision_failed, workload_completed or workload_gone — 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_releasedbooleanWhether a stopped instance's GPUs have been released back to the machine (when the balance ran out, where the machine supports it: an auto-stopped instance, or one you had stopped yourself). When true the disk is still there, and a start needs the same GPUs to be available again
gpu_release, gpu_release_requested_atstring or null, timestamp or nullWhen the platform decides to release the instance's GPUs (the balance ran out), gpu_release_requested_at records when, and gpu_release how it stands: pending (the machine has not confirmed it yet), released, or unsupported (the machine cannot release them, so they stay reserved for you; both return to null once the balance is positive again). Both are null when no release was decided
hold_price_per_hour_micro, stop_hold_price_per_hour_micro, suspended_hold_price_per_hour_micro, stop_releases_gpusinteger or null, booleannull and false on Superheat CN: a stopped instance pays storage only, and your own stop always keeps its GPUs reserved for you
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_bystringThe member who deployed it: their email; an account that signs in by phone and has no email shows as the phone number with its country code, for example +8613800138000. 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. Status goes to stopping, then stopped.running
POST /v1/instances/{id}/startBrings a stopped instance back on the same disk. 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 also re-checks the balance — it has to cover one hour of the organization's burn with this instance running — and can return 402 INSUFFICIENT_BALANCE.

A start also fails when the host cannot take it. In each case the instance is put back in stopped, with its disk:

StatusCodeWhen
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
409CAPACITY_UNAVAILABLEThe instance's GPUs were released when the balance ran out, and they are now in use by someone else or out of service on that machine. Try again later; if it keeps failing, the only way forward is to destroy it (which deletes the disk) and deploy again. See After the GPUs are released

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 superheat-cn-registry.cn-shanghai.cr.aliyuncs.com/superheat/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 a balance that covers one hour of the instance (GPU plus disk) on top of an hour of the organization's other instances.

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://console-api.superheat.cloud"
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'