HTTP
The REST API behind the SDK and CLI: sessions, jobs, volumes, billing.
Everything the Python SDK and super CLI do goes through a versioned JSON API
at https://api.supercomputer.sh/v1. Use it directly from any language or
from curl. This page documents the routes a user API key can call. Internal
and admin routes are not covered.
Authentication
Every request carries a user API key as a bearer token. Keys start with
super_sk_; get one from super login or the dashboard.
curl -s https://api.supercomputer.sh/v1/auth/identity \
-H "Authorization: Bearer $SUPERCOMPUTER_API_KEY"
# {"user_id":"u_...","role":"member","team_ids":[]}Request bodies are JSON with Content-Type: application/json. Timestamps
are RFC 3339. Money is in USD micros (1000000 = $1.00). SKU names are the
public short names from GET /v1/skus (for example h100, cpu).
Errors
Errors are JSON with an error message, often a machine-readable code,
and a request_id to quote in support requests.
{"error": "job_not_found", "code": "job_not_found", "request_id": "..."}| Status | Meaning |
|---|---|
400 | Invalid body or query. |
401 | Missing or invalid API key. |
402 / 403 | Billing block, or a feature your account cannot use. |
404 | Object missing or owned by someone else. |
409 | State conflict (session closing, spec mismatch, idempotency key reuse). |
410 | Session dead or its filesystem gone (ephemeral). |
413 | Body or file too large. |
429 | Rate limited; honour Retry-After. |
503 | Backend unavailable or session unreachable; retry after Retry-After. |
Image specs
image_spec is optional. A session or job that names no image boots the
default image for its SKU: the CUDA training image (torch, torchvision,
transformers, vLLM pinned) on any GPU box, a slim Python image on a CPU
box. These are the images the CLI and SDK boot by default, already baked
across the fleet, so they start fastest.
To boot something else, image_spec is a JSON document, sent as a string,
describing the image to build: a base registry reference and an ordered
list of layers. The smallest valid spec is a bare registry image:
{"base": "pytorch/pytorch:2.9.1-cuda12.8-cudnn9-runtime",
"layers": [{"type": "base", "params": {"image": "pytorch/pytorch:2.9.1-cuda12.8-cudnn9-runtime"}}]}Layer types are base, apt_install (packages), pip_install
(packages, optional index_url), run_commands (commands) and env
(variables). The Python SDK's Image class builds these; call
json.dumps(image.to_dict()) to get the string. Images are cached across
the fleet by the hash of the spec text, so send a spec byte for byte as
you sent it before to hit the cache.
Sessions
A session is a persistent /workspace filesystem that a host brings online
on demand. Create one, poll it until it is ready, then run work on it.
Create a session
POST /v1/sessions
| Field | Type | Notes |
|---|---|---|
sku | string | GPU or CPU type. Or sku_preference, an ordered list. |
image_spec | string | Image to boot, as a JSON string. Omit for the SKU default. See Image specs. |
cold | bool | Register the session without allocating a host. $0 until first resume. |
volume_mounts | array | [{"name", "mount_path", "read_only"}], composed at setup. |
ephemeral | bool | Run-to-completion session: workspace is never persisted, not resumable. |
checkpoint | bool | Opt in to checkpoint/restore on preemption. Off by default. |
restart | bool | Re-run the command on a new box after a host loss. Default true. |
keep_warm_seconds | int | Idle grace before the host reaps the session. |
min_disk_gb | int | Floor on the host root volume. |
stale_after_seconds | int | Server-side lifetime bound for a detached session. |
Send an Idempotency-Key header to make retries safe; see
retrying session creation.
curl -s -X POST https://api.supercomputer.sh/v1/sessions \
-H "Authorization: Bearer $SUPERCOMPUTER_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"sku": "h100"}'Replies:
202withstatus: "queued"or"allocating"andretry_after_s: no host yet. PollGET /v1/sessions/{sid}.200withstatus: "active": the allocation. Fields:sid,cid,hid,host_addr,cert_sha256,token,token_exp,engine,ephemeral.
host_addr, token and cert_sha256 are what the SDK uses to open the
host WebSocket for exec, spawn and streaming. That protocol is not
documented here; use the Python SDK to run commands, and the HTTP routes
below for everything around them.
Read a session
GET /v1/sessions/{sid}
The status code tracks the lifecycle so pollers never infer it from shape:
| Status | Body |
|---|---|
200 | Ready: the allocation above with a fresh token, or the full record for a cold or closed session. |
202 | queued or allocating; poll again after retry_after_s. Polling a queued session also keeps it alive. |
410 | dead, with detail. |
503 | unreachable; Retry-After: 5. |
Every shape carries sid, status, activity, detail, sku_preference,
setup_acked, client_attached, created_at and the stage timestamps.
List sessions
GET /v1/sessions?status=active&limit=100&before=<rfc3339>&include_retired=1
status defaults to active. Returns {"sessions": [...], "limit", "has_more", "next_before"}; pass next_before as before for the next page. Limit is
capped at 500.
Lifecycle actions
| Route | Body | Effect |
|---|---|---|
POST /v1/sessions/{sid}/resume | {"sku", "image_spec", "volume_mounts"} all optional | Bring a cold or stopped session online. Same reply shapes as create. image_spec re-baselines the image. |
DELETE /v1/sessions/{sid} | none | Stop the session. Billing stops now; files stay. {"ok": true, "sid", "status"}. |
POST /v1/sessions/{sid}/token | none | Mint a fresh host token: {"sid", "cid", "hid", "token", "token_exp"}. |
POST /v1/sessions/{sid}/requeue | none | You were allocated a host you cannot reach: unbind and re-place. 409 if attached; 429 after 3 requeues. |
POST /v1/sessions/{sid}/migrate | {} | Checkpoint and move to a fresh host. Poll the session for the new placement. |
POST /v1/sessions/{sid}/volumes | {"volume_mounts": [...]} or one {"name", "mount_path", "read_only"} | Attach volumes to a live session. |
DELETE /v1/sessions/{sid}/volumes/{name} | none | Flush and detach one volume. |
Files on a live session
These go through the host, for callers that cannot open the WebSocket.
POST /v1/sessions/{sid}/fileswith{"files": [{"path", "content_b64", "mode"}]}writes files into/workspace. Reply:{"ok", "written", "skipped"}.GET /v1/sessions/{sid}/files/{path}returns the file bytes, typed by extension. Files over the host inline cap return413 file_too_large; read those through/v1/fsafter the next publish.
Processes and logs
Durable, served from object storage, so they work after the host is gone.
GET /v1/sessions/{sid}/processesreturns{"processes": [...], "session_status"}. Each process hasprocess_id,command(argv list),status,exit_code,started_at_ms,ended_at_ms,output_bytes.GET /v1/sessions/{sid}/processes/{pid}/logs?stream=stdout|stderr|both&output=all|tail&output_bytes=N&offset=Nreturns raw log bytes.GET /v1/sessions/{sid}/logsreturns the tail of every process's log, concatenated under==> command [pid] status <==headers.
Cost and history
GET /v1/sessions/{sid}/costreturns{"sid", "compute_usd_micros", "billable_seconds", "intervals": [{"state", "billable", "started_at", "ended_at", "seconds", "sku", "price_usd_micros_per_hour", "amount_usd_micros", "open"}]}.GET /v1/sessions/{sid}/history?limit=Nreturns{"sid", "status", "lifecycle", "activity", "events": [...], "count", "truncated"}.
Jobs
A job fans one or more commands out as a durable sweep. Each task runs in its own session. See Batch for the model.
Submit a job
POST /v1/jobs
| Field | Type | Notes |
|---|---|---|
commands | string[] | Required. One task per command, fully resolved. |
name | string | Resubmitting the same name and spec returns the existing job; a changed spec is 409. |
image_spec | string | Image to boot. Omit for the SKU default; from_session inherits the session's image. |
from_session | string | Fork the workspace of a persistent session you own. |
sku / sku_preference | string / string[] | Hardware. |
volume_mounts | array | Same shape as sessions. |
env | object | Environment for every task. |
max_parallel | int | Fan-out cap. Default 8. |
retries | int | Per-task retries. Default 2. |
no_canary | bool | Skip the one-task smoke run before fan-out. |
canary_command | string | Run this instead of task 0 as the gate. |
after | string[] | Job names or ids to wait on. after_strict makes partial success block. |
not_before, deadline | RFC 3339 | Scheduling window. |
expected_runtime_s, priority | int | Hints; priority > 0 is the fast lane. |
checkpoint | bool | Tri-state checkpoint policy, as for sessions. |
webhook_url | string | POSTed the job status document on completion. |
Reply: 200 {"job": {...}, "created": true|false}. Inline workspace
manifests are also accepted but require uploading blobs first; use the SDK
for that path.
curl -s -X POST https://api.supercomputer.sh/v1/jobs \
-H "Authorization: Bearer $SUPERCOMPUTER_API_KEY" \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{"name": "lr-sweep", "sku": "h100",
"commands": ["python train.py --lr 1e-3", "python train.py --lr 3e-4"]}
EOFRead and manage jobs
{id} accepts a job id or a job name you own.
| Route | Reply |
|---|---|
GET /v1/jobs | {"jobs": [...]} with per-state task counts. |
GET /v1/jobs/{id}?cost=1 | {"job", "counts", "tasks", "clusters", "blocked", "window_phase"}. cost=1 adds a per-session cost rollup. |
GET /v1/jobs/{id}/attempts?task=N | Every session that ran task N, newest first. |
POST /v1/jobs/{id}/cancel body {"remaining": bool} | {"ok", "canceled"}. remaining leaves running tasks alone. |
POST /v1/jobs/{id}/retry body {"all": bool} | Re-queue failed tasks; all also recovers canceled and wedged tasks. |
POST /v1/jobs/{id}/settings body {"max_parallel", "priority"} | Change a live sweep's fan-out or lane. |
POST /v1/jobs/{id}/gc | Retire a finished sweep's sessions. 409 job_not_terminal while running. |
Job states: waiting, canarying, running, canceling, succeeded,
succeeded_partial, failed, canceled. Task states: pending,
launching, running, succeeded, failed, canceled. Each task carries
index, command, state, attempts, exit_code, error_sig,
session_id, process_id.
Volumes
Named, versioned filesystems you mount into sessions.
| Route | Reply |
|---|---|
POST /v1/volumes body {"name"} | 201 {"volume": {...}} |
POST /v1/volumes/{name}/import-session body {"session_id", "source_path", "dest_path"} | Copy a stopped session's files into the volume. 409 while either is active. |
GET /v1/volumes | {"volumes": [{"volume_id", "name", "version", "created_at", "updated_at"}]} |
GET /v1/volumes/{name} | {"volume": {...}} |
DELETE /v1/volumes/{name} | {"ok": true} |
GET /v1/volumes/{name}/files | {"volume", "files": [{"path", "hash", "size", "type", "updated_at"}]} |
GET /v1/volumes/{name}/fs/{path} | Browse or download, see below. |
Filesystem browse
Session workspaces and volumes are served read-only from object storage:
GET /v1/fs/{sid}/andGET /v1/volumes/{name}/fs/list a directory as HTML. Add?format=jsonfor{"files": [{"path", "size", "hash"}]}under that prefix.GET /v1/fs/{sid}/path/to/fileredirects to a short-lived signed download URL. Range requests work.POST /v1/fs/{sid}/mount-urlreturns{"token", "path", "expires_at"}. Mounthttps://api.supercomputer.sh{path}with rclone or any WebDAV-less HTTP mount; no header needed. Ephemeral sessions return410.
An active session lists files as of its last background sync. A closing session waits for the final manifest.
Catalog
GET /v1/skusreturns{"skus": [{"name", "label", "vram_gb", "vcpus", "ram_gb", "cost_per_hr", "availability", "effective_concurrency"}], "catalog_status"}.availabilityislivewhen idle hosts exist now.GET /v1/billing/pricesreturns{"prices": [...]}, the current rate card by SKU.GET /v1/healthis unauthenticated:{"ok": true, "service": "dispatcher-v2"}.GET /v1/auth/identityreturns{"user_id", "role", "team_ids"}for the key in use.
Billing
| Route | Reply |
|---|---|
GET /v1/billing/balance | {"balance_usd_micros", "available_usd_micros", "accrued_unposted_usd_micros", "live_rate_usd_micros_per_hour", "spend_blocked", "autorefill", "card", "plan"} |
GET /v1/billing/usage?since=&until= | Rollup rows with totals_by_resource, totals_by_session, totals_by_api_key. Bounds are RFC 3339 or a duration like 24h; default is the last 30 days. |
GET /v1/billing/ledger?before_seq=&limit= | {"entries": [...], "next_before_seq"}, newest first, limit up to 200. |
GET /v1/billing/payment-method | The card on file. |
POST /v1/billing/checkout body {"amount_usd_micros", "success_url", "cancel_url"} | {"url", "purchase_id", "checkout_session_id"}; send the user to url to pay. |
PUT /v1/billing/autorefill | {"enabled", "threshold_usd_micros", "amount_usd_micros", "weekly_cap_usd_micros"}; returns the balance view. |
DELETE /v1/billing/payment-method | Remove the card. |
Copy groups
POST /v1/copy-groups takes a session create body plus "size": 2..4 and
places that many identical sessions together for multi-node work. Reply:
202 {"group_id", "sessions": [...]}. GET /v1/copy-groups/{id} returns
{"group", "sessions"} and keeps queued members alive.
Client headers
The SDK sends X-Supercomputer-Client and X-Supercomputer-Client-Version.
They are optional. A client that sends a version below the deployment's
floor gets 426 client_too_old; a request with no version header is never
refused on that basis.