Skip to main content

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​

MethodPathWhat it does
GET/gpu-instances/availabilityFree GPUs per region and what one GPU comes with
GET/platform/gpu-pricesPrice per GPU-hour by type (no auth needed)
GET/gpu-instancesYour instances: everything live, plus stopped ones from the last 7 days
POST/gpu-instancesLaunch
GET/gpu-instances/{id}One instance, with the credit currently held for it
POST/gpu-instances/{id}/terminateStop it
POST/gpu-instances/{id}/extendAdd 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/balanceAvailable 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​

FieldTypeRequiredNotes
region_idstringyesFrom availability
gpu_typestringyesFrom availability, exactly
gpu_countintyes1–8, not more than the region's gpu_per_node
container_imagestringyesOne 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_hoursintno0 (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.
namestringnoShown in lists
startup_commandstringnoOverrides the image's command. sleep infinity keeps an image alive that would otherwise exit
init_scriptstringnoShell run once at boot, before the startup command (apt/pip installs). See Init scripts
ssh_enabledboolnoSet true with ssh_public_key to get an SSH command back. Only works with an image that includes OpenSSH (platform images do)
ssh_public_keystringnoOne OpenSSH public key (ssh-ed25519 … / ssh-rsa …)
service_portintnoExpose this container port at https://api.ecohash.com/gpu-instances/{id}/service/ (authenticated with the same key)
interruptibleboolnoCheaper tier the platform may preempt; only where a rate is listed
template_idintnoLaunch from a template
cloud_drives, new_cloud_drives, shared_filesystems, new_shared_filesystemsarraysnoAttach 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.

FieldMeaning
id, name
statuspending → 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_portWhat you launched
hourly_rate_usdRate for the whole instance (per GPU × count)
interruptibleWhich tier it was sold as
estimated_duration_hoursnull = no end time (billed hourly); a number = fixed run
ssh_enabled, ssh_tunnel_host, ssh_tunnel_port, ssh_ready, ssh_commandssh_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_urlFor the Jupyter platform image
created_at, started_at, terminated_atBilling runs from started_at (the pod is up), not from creation
termination_reasonuser, duration_expired, credit_depleted, preempted, failed, admin
created_by_user_idWhich 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.

Your privacy choices

Essential cookies are always on. You can change these choices at any time.