GPU instances (Compute API)
Everything you can do with GPU instances in the console — launch, list, get the SSH command, extend, terminate — is available over the API with an API key that has Compute access. It is the same product: same endpoints the console uses, same prices, same credit holds and refunds, same limits on your account.
This page covers GPU instances. See also GPU clusters, Storage and SSH keys.
Before you start
A key with Compute access (Read-only to view and quote, Full to launch) and credit — see the Compute API overview, which also has the shared rules: auth, region ids, billing, rate limits, errors.
https://api.ecohash.com
Authorization: Bearer eco_YOUR_KEY
The lifecycle in five calls
KEY="eco_YOUR_KEY"
API="https://api.ecohash.com"
# 1. What can I launch, where, for how much?
curl -s $API/gpu-instances/availability -H "Authorization: Bearer $KEY"
curl -s $API/platform/gpu-prices
# 2. Launch — no end time, billed hour by hour until you terminate it.
# A platform image: it includes OpenSSH, which SSH access needs.
curl -s -X POST $API/gpu-instances \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{
"name": "api-test",
"region_id": "<region id from availability>",
"gpu_type": "<gpu_type from availability>",
"gpu_count": 1,
"container_image": "public.ecr.aws/a2b7e2y7/ecolink/gpu-base:v0.1.0",
"estimated_duration_hours": 0,
"ssh_enabled": true,
"ssh_public_key": "ssh-ed25519 AAAA... you@laptop"
}'
# → 202 with the instance; note its "id"
# 3. Poll until "ssh_ready": true — "ssh_command" appears with it
curl -s $API/gpu-instances/<id> -H "Authorization: Bearer $KEY"
# 4. Connect
ssh -o HostKeyAlias=ecohash-instance-<id> -p <port> root@<host> # exactly the "ssh_command" string
# 5. Stop it — GPUs released, unused held credit refunded
curl -s -X POST $API/gpu-instances/<id>/terminate -H "Authorization: Bearer $KEY"
Endpoints
| Method | Path | What it does |
|---|---|---|
GET | /gpu-instances/availability | Free GPUs per region and what one GPU comes with |
GET | /platform/gpu-prices | Price per GPU-hour by type (no auth needed) |
GET | /gpu-instances | Your instances: everything live, plus ended ones from the last 7 days (add ?ended=all for every ended one) |
POST | /gpu-instances | Launch |
GET | /gpu-instances/{id} | One instance, with the credit currently held for it |
POST | /gpu-instances/{id}/terminate | Stop it |
POST | /gpu-instances/{id}/extend | Add hours to a fixed-duration instance |
PATCH | /gpu-instances/{id} | Change image / command / init script / service port and rebuild |
DELETE | /gpu-instances/{id} | Hide a terminated or failed instance from the list (billing history is kept) |
GET | /billing/balance | Available credit |
The web terminal, file upload and file browser are console-only; over the API you connect with SSH.
GET /gpu-instances/availability
One row per region. available is live — it is how many GPUs a launch can get right now.
[
{
"id": "atl",
"name": "Atlanta",
"gpu_type": "NVIDIA-RTX-PRO-6000-Blackwell-Server-Edition",
"gpu_per_node": 8,
"total_nodes": 7,
"available": 44,
"node_count": 7,
"vcpu_per_gpu": 16,
"ram_gb_per_gpu": 64,
"scratch_disk_gib": 400
}
]
Use id as region_id and gpu_type exactly as returned when launching.
GET /platform/gpu-prices
[
{
"gpu_type": "NVIDIA-RTX-PRO-6000-Blackwell-Server-Edition",
"display_name": "RTX Pro 6000",
"hourly_rate_usd": 1.89,
"vram_gb": 96
}
]
On-demand prices only. Spot, where it is offered to your account, is priced on the availability rows — see Spot instances.
POST /gpu-instances
| Field | Type | Required | Notes |
|---|---|---|---|
region_id | string | yes | From availability |
gpu_type | string | yes | From availability, exactly |
gpu_count | int | yes | 1–8, not more than the region's gpu_per_node |
container_image | string | yes | One of the platform images (public.ecr.aws/a2b7e2y7/ecolink/gpu-base:v0.1.0 for general work — the full list is under Recommended base images) or any public image. A private image works too — register it first with its credentials, see Container images. SSH needs OpenSSH in the image; platform images have it, most third-party images don't |
estimated_duration_hours | int | no | 0 (or omitted) = no end time, billed hourly until you terminate it. 1–72 = fixed run, held up front, auto-stops when it elapses. See Duration. |
name | string | no | Shown in lists |
startup_command | string | no | Overrides the image's command. sleep infinity keeps an image alive that would otherwise exit |
init_script | string | no | Shell run once at boot, before the startup command (apt/pip installs). See Init scripts |
ssh_enabled | bool | no | Set true with ssh_public_key to get an SSH command back. Only works with an image that includes OpenSSH (platform images do) |
ssh_public_key | string | no | One OpenSSH public key (ssh-ed25519 … / ssh-rsa …) |
service_port | int | no | Expose this container port at https://api.ecohash.com/gpu-instances/{id}/service/ (authenticated with the same key) |
interruptible | bool | no | true launches a spot instance: cheaper, but its GPU can be taken at any time — with a duration it pauses and resumes, with no end time it stops. Only where availability shows a spot_hourly_rate_usd |
template_id | int | no | The template this launch came from. It records where the launch came from and does not fill in any other field: send the template's spec fields with it |
user_model_id | int | no | Mount a registered model's weights. Your own model: its shared filesystem, writable. A model another account published: that model's directory only, read-only, at the path its startup command uses. The instance must be in the model's region; any other is refused with a message naming it |
cloud_drives, new_cloud_drives, shared_filesystems, new_shared_filesystems | arrays | no | Attach or create storage at launch. See Storage. A mount path that overlaps the model weights' path is refused |
Returns 202 Accepted with the instance (status pending). The credit hold is taken in the same request — if it cannot be, nothing is created and you get 402.
Spot instances
A spot instance costs less per GPU-hour than on-demand, in exchange for one rule: its GPU can be taken at any time when the capacity is needed, with about 5 seconds' notice. What happens then depends on its duration.
- Spot is offered only where your account's
GET /gpu-instances/availabilityrows showspot_hourly_rate_usd. Launch with"interruptible": true. spot_availableis how many GPUs a spot launch can take now, andspot_max_gpus_per_instanceis the largest spot instance: 1, 2 or 4 GPUs. Your account can run a limited number of spot instances and GPUs at once; a launch over a limit is refused with409and codespot_limit.- With a fixed duration (
estimated_duration_hours1–72), it pauses and comes back on its own:- while paused its status is
preempted,paused_atsays since when, and it is not billed; - it resumes automatically when a GPU frees up, restarted from its image, with its drives and filesystems attached;
- it runs until its running time reaches the duration you bought, however many times it is paused. Time spent paused is added to its end time (
paused_secondsis the total so far); - you get a notification each time it pauses and resumes;
- there is no limit on how long it waits. Terminate it at any time: you are billed only for the time it ran, and the rest of the hold is refunded.
- while paused its status is
- With no end time, it does not come back. Its status becomes
terminatedwithtermination_reasonspot_reclaimed, you get a notification, and you are billed only until it stopped. The rest of the hold is refunded. - Either way, memory, running processes and the container's own disk are lost. Use spot for work you can restart: checkpoint to a cloud drive or shared filesystem.
- Spot isn't available for clusters, model instances or reserved nodes.
The instance object
Returned by list, get, launch and terminate.
| Field | Meaning |
|---|---|
id, name | |
status | pending → running → terminating_requested → terminated; also terminating_low_balance, failed, and preempted while a spot instance is paused |
region_id, gpu_type, gpu_count, container_image, startup_command, init_script, service_port | What you launched |
user_model_id | The model whose weights are mounted; absent when none |
hourly_rate_usd | Rate for the whole instance (per GPU × count) |
interruptible | true for a spot instance |
estimated_duration_hours | null = no end time (billed hourly); a number = fixed run |
paused_seconds, paused_at | Spot with a fixed duration: total time spent paused so far (added to the end time, not billed), and since when it is paused now (null while running) |
ssh_enabled, ssh_tunnel_host, ssh_tunnel_port, ssh_ready, ssh_command | ssh_ready is a live check: true once the instance actually accepts SSH connections. ssh_command (ssh -o HostKeyAlias=ecohash-instance-<id> -p <port> root@<host>; connect as root with the key you passed) is present only when ssh_ready is true — poll until it appears, usually within a minute of running. If it never appears, the image has no OpenSSH (see SSH access) |
ssh_host_fingerprint, ssh_known_hosts_line | The instance's own SSH host key: its SHA256:… fingerprint, to check on the first connection, and the line to add to ~/.ssh/known_hosts so a script connects without a prompt. Kept for the instance's whole life. Absent until the instance has been started with its own key |
jupyter_url | For the Jupyter platform image |
created_at, started_at, terminated_at | Billing runs from started_at (the pod is up), not from creation |
termination_reason | user, duration_expired, credit_depleted, spot_reclaimed (a spot instance stopped because its capacity was needed), failed, admin |
created_by_user_id | Which member of the account launched it |
GET /gpu-instances/{id} wraps it: { "instance": {…}, "held_amount": 1.89, "cloud_drives": […], "shared_filesystems": […] }.
These are the fields we commit to. Anything else that appears in a response is not part of the contract and may change; fields are only ever added, never renamed or removed.
POST /gpu-instances/{id}/terminate
No body. Returns 202 with the instance in terminating_requested. The pod stops within seconds; the unused part of the hold is refunded once it has. 409 if the instance is not running.
POST /gpu-instances/{id}/extend
{ "hours": 2 }
Fixed-duration instances only. Holds hours × hourly_rate_usd now and moves the auto-stop time. Returns 400 for an instance with no end time — it has nothing to extend.
PATCH /gpu-instances/{id}
Any of container_image, startup_command, init_script, service_port. The instance is rebuilt with the new values; GPUs, region and billing are unchanged. See Redeploy.
Billing, rate limits, errors
As on the overview. In short: no end time = first hour held, then settled and re-held hourly, stops at $0; fixed duration = whole run held, auto-stop, early-terminate refund. 60 requests/min per key, 10 launches/min per account.
Python example
import time, requests
API, KEY = "https://api.ecohash.com", "eco_YOUR_KEY"
H = {"Authorization": f"Bearer {KEY}"}
regions = requests.get(f"{API}/gpu-instances/availability", headers=H).json()
r = next(x for x in regions if x["available"] > 0)
inst = requests.post(f"{API}/gpu-instances", headers=H, json={
"name": "from-python",
"region_id": r["id"],
"gpu_type": r["gpu_type"],
"gpu_count": 1,
"container_image": "public.ecr.aws/a2b7e2y7/ecolink/gpu-base:v0.1.0", # has OpenSSH
"estimated_duration_hours": 0, # no end time
"ssh_enabled": True,
"ssh_public_key": open("~/.ssh/id_ed25519.pub").read().strip(),
}).json()
while True:
d = requests.get(f"{API}/gpu-instances/{inst['id']}", headers=H).json()["instance"]
if d["status"] == "running" and d.get("ssh_command"):
print(d["ssh_command"]); break
if d["status"] in ("failed", "terminated"):
raise SystemExit(d.get("termination_reason"))
time.sleep(5)
# ... work over SSH ...
requests.post(f"{API}/gpu-instances/{inst['id']}/terminate", headers=H)
Other compute resources
GPU clusters · Storage — cloud drives and shared filesystems · SSH keys · Templates: list, quote, and launch from one.
Working from Claude Code, Codex or Claude Desktop? The same actions are available as tools — see GPU MCP server.