The DeployGPU API launches GPU virtual machines on QuantaCloud, Massed Compute, Verda and Hyperstack with one key and one balance: find an offer, create a deployment, poll until it is active, SSH in, and stop it when the job is done. Billing is the same as in the dashboard, so the prepaid balance caps what a runaway script can spend.
What the API can and cannot do
| You can | Endpoint |
|---|---|
| Search live offers, no key needed | GET /api/v1/offers, GET /api/v1/offers/{id} |
| List GPU families and providers, no key needed | GET /api/v1/gpu-families, GET /api/v1/providers |
| See a provider's OS images and volume types, no key needed | GET /api/v1/providers/{provider}/images, .../volume-types |
| Launch, list, inspect and stop VMs | POST /api/v1/deployments, GET /api/v1/deployments, GET /api/v1/deployments/{id}, POST /api/v1/deployments/{id}/stop |
| Power a Verda or Hyperstack VM off and on without deleting it, with billing continuing | POST /api/v1/deployments/{id}/shutdown, .../start, GET .../power |
| Create, list and delete volumes on Verda and Hyperstack | /api/v1/volumes |
| Manage SSH keys | /api/v1/users/me/ssh-keys |
| Read your account and balance | GET /api/v1/account, GET /api/v1/account/balance |
The API is deliberately narrower than the dashboard. It launches plain VMs only: no app templates, no private app links, and no Vast.ai containers, which return provider_not_supported. Volumes are attached at launch through volume_ids, never to a running VM. The power endpoints work on Verda and Hyperstack and return provider_not_supported elsewhere: they keep the VM and its disk, and full billing continues while the VM is off, so they are a recovery tool, not a pause. Credits and API keys are managed in the dashboard. There is no CLI, SDK or webhook today.
The base URL is https://api.deploygpu.ai. The full reference lives in the API documentation, and the OpenAPI spec is at https://api.deploygpu.ai/api-docs.
Keys
Create a key on the API keys page. Keys start with gpu_live_ and go in either header:
curl -s https://api.deploygpu.ai/api/v1/account/balance \
-H "X-API-Key: $DEPLOYGPU_API_KEY"
curl -s https://api.deploygpu.ai/api/v1/account/balance \
-H "Authorization: Bearer $DEPLOYGPU_API_KEY"
A key has no scopes and no expiry date: it can do everything your account can do, including launching GPUs. Give each system its own key, keep it in a secret manager rather than a repository, and delete keys you no longer use. A request without a valid key gets 401 with an empty body.
A full launch, step by step
- Find an offer. The offer search is public:
curl -s "https://api.deploygpu.ai/api/v1/offers?gpu=rtx-a6000&sort_by=priceHourly&sort_order=asc&size=20"
Keep offers whose deploymentType is virtual. The gpu filter takes variant slugs such as rtx-a6000, h100-sxm-80gb or h100-pcie-80gb, and GET /api/v1/gpu-families lists them all.
- Create the deployment with the offer's
idand the provider's key:
curl -s -X POST https://api.deploygpu.ai/api/v1/deployments \
-H "X-API-Key: $DEPLOYGPU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"provider": "quantacloud", "offer_id": "OFFER_ID", "disk_gb": 100}'
The answer is 202 with {"id": "...", "status": "provisioning"}. The provider values the create call accepts are quantacloud, masscompute, verda and hyperstack. Offers name Massed Compute massed-compute, so map that one before you send it. disk_gb only matters on Verda: the other providers use the disk that comes with the offer. Leave out ssh_key_ids to install every key on your account, or list the ones you want.
- Poll until the status is
active:
curl -s https://api.deploygpu.ai/api/v1/deployments/DEPLOYMENT_ID \
-H "X-API-Key: $DEPLOYGPU_API_KEY"
For about a second after the 202, this can return 404, so treat an early 404 as "not yet". Most VMs are active within 1 to 3 minutes. The connection object then holds public_ip, ssh_port and ssh_user, and the user differs by provider: ubuntu, Ubuntu or root.
-
SSH in. On some providers a VM reports active a little before SSH accepts connections, so retry for a minute before treating a refused connection as a failure.
-
Stop it when the work is done.
DELETE /api/v1/deployments/{id}does the same:
curl -s -X POST https://api.deploygpu.ai/api/v1/deployments/DEPLOYMENT_ID/stop \
-H "X-API-Key: $DEPLOYGPU_API_KEY"
Stopping deletes the VM and its disk, and the unused seconds of the current hour go back to your balance.
The same flow in Python
This version tries the cheapest offers in order, never repeats a create call blindly, and always stops what it started:
import os
import time
import requests
API = "https://api.deploygpu.ai/api/v1"
HEADERS = {"X-API-Key": os.environ["DEPLOYGPU_API_KEY"]}
# Offers call Massed Compute "massed-compute"; the create call wants "masscompute".
PROVIDER_KEY = {"massed-compute": "masscompute"}
VM_PROVIDERS = {"quantacloud", "masscompute", "verda", "hyperstack"}
def offers(gpu_slug):
params = {"gpu": gpu_slug, "sort_by": "priceHourly", "sort_order": "asc", "size": 20}
resp = requests.get(f"{API}/offers", params=params, timeout=30)
resp.raise_for_status()
for offer in resp.json()["offers"]:
provider = PROVIDER_KEY.get(offer["provider"]["slug"], offer["provider"]["slug"])
if offer["deploymentType"] == "virtual" and provider in VM_PROVIDERS:
yield provider, offer
def create(provider, offer):
body = {"provider": provider, "offer_id": offer["id"], "disk_gb": 100}
resp = requests.post(f"{API}/deployments", json=body, headers=HEADERS, timeout=60)
if resp.status_code in (400, 503):
code = resp.json()["error"]["code"]
if code in ("offer_unavailable", "offer_cooling_down"):
return None # this offer is gone or cooling down: try the next one
resp.raise_for_status()
return resp.json()["id"]
def wait_until_active(deployment_id, timeout_s=900):
deadline = time.time() + timeout_s
while time.time() < deadline:
resp = requests.get(f"{API}/deployments/{deployment_id}", headers=HEADERS, timeout=30)
if resp.status_code != 404: # a 404 right after create means "not visible yet"
resp.raise_for_status()
deployment = resp.json()
if deployment["status"] == "active":
return deployment
if deployment["status"] in ("failed", "terminated", "interrupted"):
raise RuntimeError(f"{deployment['failure_code']}: {deployment['suggested_action']}")
time.sleep(10)
raise TimeoutError(deployment_id)
deployment_id = None
for provider, offer in offers("rtx-a6000"):
deployment_id = create(provider, offer)
if deployment_id:
break
if deployment_id is None:
raise SystemExit("No RTX A6000 offer could be launched right now")
try:
conn = wait_until_active(deployment_id)["connection"]
print(f"ssh {conn['ssh_user']}@{conn['public_ip']} -p {conn['ssh_port']}")
# ... run your job over SSH here ...
finally:
requests.post(f"{API}/deployments/{deployment_id}/stop", headers=HEADERS, timeout=60)
The one rule in that script that matters most: never retry POST /api/v1/deployments automatically after a timeout or a dropped connection. The API has no idempotency key, so a retried create can start, and bill, a second GPU. If a create call times out, list your deployments with GET /api/v1/deployments before you try again. The provisioning failures guide goes through every failure path in more detail.
Errors worth handling
Errors come back as {"error": {"code": "...", "message": "..."}}.
| Status | Code | What to do |
|---|---|---|
| 400 | offer_unavailable | The offer sold out between your search and the create. Try the next offer |
| 400 | validation_error, invalid_ssh_key, image_incompatible, image_not_offered | Fix the request. Retrying the same body will fail again |
| 400 | provider_not_supported | The offer is a container (Vast.ai). Pick a virtual offer |
| 400 | offer_provider_mismatch | The provider value does not match the offer. Check the Massed Compute mapping above |
| 402 | insufficient_balance | Your balance must cover one hour of the offer. Add credits |
| 429 | rate_limited | Slow down |
| 503 | offer_cooling_down | That offer just failed for capacity and is paused for about a minute. Wait for Retry-After or try another offer |
A deployment that fails after the 202 carries failure_code, retryable and suggested_action, and the reserved first hour is refunded automatically.
Limits and billing
The limit is 3,000 requests per 15 minutes per IP, and every response carries the remaining count in the X-Rate-Limit-Remaining header. The offer search refreshes from the providers every minute, so polling it more often than that gains nothing.
A deployment launched through the API is billed exactly like one launched in the dashboard: the first hour is reserved at launch, each later hour is charged as it starts, and unused seconds come back when you stop. There is no idle timer, so a script that crashes before its stop call leaves a GPU running. The finally block above is the cheapest insurance, and the stop guide has a guardrail script that stops anything running longer than a limit you set. The pricing page covers top-ups and fees.
Frequently asked questions
Which providers can I launch on through the DeployGPU API?
QuantaCloud, Massed Compute, Verda and Hyperstack, as virtual machines. Vast.ai containers are available only in the dashboard.
Can I launch Jupyter, ComfyUI or Open WebUI through the API?
No. App templates and their private links are dashboard-only. Through the API you launch a plain VM and install what you need over SSH.
Is it safe to retry a create request?
Not blindly. There is no idempotency key, so a retried create can launch and bill a second GPU. List your deployments first and retry only if nothing was created.
How do I know when a deployment is ready?
Poll the deployment until its status is active, then read the SSH user, IP address and port from the connection field. Most VMs get there in 1 to 3 minutes.
Can I limit what an API key is allowed to do?
Not today. Keys have no scopes or expiry, so use one key per system, store it as a secret and delete it when it is no longer needed.