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
Compute access on the account, a key created with Compute access, 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 -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 stopped ones from the last 7 days |
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,
"interruptible_hourly_rate_usd": null,
"vram_gb": 96
}
]
interruptible_hourly_rate_usd is null where the cheaper, preemptible tier is not sold for that GPU type — do not send "interruptible": true for it.
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 | Cheaper tier the platform may preempt; only where a rate is listed |
template_id | int | no | Launch from a template |
cloud_drives, new_cloud_drives, shared_filesystems, new_shared_filesystems | arrays | no | Attach or create storage at launch. See Storage |
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.
The instance object
Returned by list, get, launch and terminate.
| Field | Meaning |
|---|---|
id, name | |
status | pending → running → terminating_requested → terminated; also preempted (interruptible tier, will resume), terminating_low_balance, failed |
region_id, gpu_type, gpu_count, container_image, startup_command, init_script, service_port | What you launched |
hourly_rate_usd | Rate for the whole instance (per GPU × count) |
interruptible | Which tier it was sold as |
estimated_duration_hours | null = no end time (billed hourly); a number = fixed run |
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 -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) |
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, preempted, 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 (read): GET /compute-templates, GET /compute-templates/public, GET /compute-templates/{id}, POST /compute-templates/quote.
Working from Claude Code, Codex or Claude Desktop? The same actions are available as tools — see GPU MCP server.